refactor: heavy changes, use new norm rebuild components

This commit is contained in:
2024-01-10 02:45:09 +08:00
parent 4e73182dd9
commit f0ec24f5df
26 changed files with 2200 additions and 1330 deletions

View File

@@ -23,6 +23,8 @@
package com.highcapable.flexiui package com.highcapable.flexiui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -43,6 +45,22 @@ data class Colors(
var textSecondary: Color var textSecondary: Color
) )
/**
* Descriptor for [Colors].
*/
@Stable
internal enum class ColorsDescriptor {
BackgroundPrimary,
BackgroundSecondary,
ForegroundPrimary,
ForegroundSecondary,
ThemePrimary,
ThemeSecondary,
ThemeTertiary,
TextPrimary,
TextSecondary
}
internal val DefaultLightColors = Colors( internal val DefaultLightColors = Colors(
backgroundPrimary = Color(0xFFF5F5F5), backgroundPrimary = Color(0xFFF5F5F5),
backgroundSecondary = Color(0xFFFAFAFA), backgroundSecondary = Color(0xFFFAFAFA),
@@ -420,3 +438,30 @@ fun blueColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when
} }
internal val LocalColors = staticCompositionLocalOf { DefaultLightColors } internal val LocalColors = staticCompositionLocalOf { DefaultLightColors }
/**
* Gets a [Color] from descriptor.
* @see ColorsDescriptor.toColor
* @param value the descriptor.
* @return [Color]
*/
internal fun Colors.fromDescriptor(value: ColorsDescriptor) = when (value) {
ColorsDescriptor.BackgroundPrimary -> backgroundPrimary
ColorsDescriptor.BackgroundSecondary -> backgroundSecondary
ColorsDescriptor.ForegroundPrimary -> foregroundPrimary
ColorsDescriptor.ForegroundSecondary -> foregroundSecondary
ColorsDescriptor.ThemePrimary -> themePrimary
ColorsDescriptor.ThemeSecondary -> themeSecondary
ColorsDescriptor.ThemeTertiary -> themeTertiary
ColorsDescriptor.TextPrimary -> textPrimary
ColorsDescriptor.TextSecondary -> textSecondary
}
/**
* Converts a descriptor to a [Color].
* @see Colors.fromDescriptor
* @return [Color]
*/
@Composable
@ReadOnlyComposable
internal fun ColorsDescriptor.toColor() = LocalColors.current.fromDescriptor(this)

View File

@@ -78,18 +78,38 @@ fun FlexiTheme(
* Defaults of Flexi UI theme styles. * Defaults of Flexi UI theme styles.
*/ */
object FlexiTheme { object FlexiTheme {
/**
* Retrieves the current [Colors] at the call site's position in the hierarchy.
* @return [Colors]
*/
val colors: Colors val colors: Colors
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalColors.current get() = LocalColors.current
/**
* Retrieves the current [Shapes] at the call site's position in the hierarchy.
* @return [Shapes]
*/
val shapes: Shapes val shapes: Shapes
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalShapes.current get() = LocalShapes.current
/**
* Retrieves the current [Typography] at the call site's position in the hierarchy.
* @return [Typography]
*/
val typography: Typography val typography: Typography
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalTypography.current get() = LocalTypography.current
/**
* Retrieves the current [Sizes] at the call site's position in the hierarchy.
* @return [Sizes]
*/
val sizes: Sizes val sizes: Sizes
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable

View File

@@ -26,8 +26,12 @@ package com.highcapable.flexiui
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
/** /**
@@ -40,6 +44,16 @@ data class Shapes(
val tertiary: CornerBasedShape val tertiary: CornerBasedShape
) )
/**
* Descriptor for [Shapes].
*/
@Stable
internal enum class ShapesDescriptor {
Primary,
Secondary,
Tertiary
}
internal val LocalShapes = staticCompositionLocalOf { DefaultShapes } internal val LocalShapes = staticCompositionLocalOf { DefaultShapes }
internal val DefaultShapes = Shapes( internal val DefaultShapes = Shapes(
@@ -47,3 +61,24 @@ internal val DefaultShapes = Shapes(
secondary = RoundedCornerShape(10.dp), secondary = RoundedCornerShape(10.dp),
tertiary = CircleShape tertiary = CircleShape
) )
/**
* Gets a [Shape] from descriptor.
* @see ShapesDescriptor.toShape
* @param value the descriptor.
* @return [Shape]
*/
internal fun Shapes.fromDescriptor(value: ShapesDescriptor): Shape = when (value) {
ShapesDescriptor.Primary -> primary
ShapesDescriptor.Secondary -> secondary
ShapesDescriptor.Tertiary -> tertiary
}
/**
* Converts a descriptor to a [Shape].
* @see Shapes.fromDescriptor
* @return [Shape]
*/
@Composable
@ReadOnlyComposable
internal fun ShapesDescriptor.toShape(): Shape = LocalShapes.current.fromDescriptor(this)

View File

@@ -23,10 +23,14 @@
package com.highcapable.flexiui package com.highcapable.flexiui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
/** /**
* Sizes defines for Flexi UI. * Sizes defines for Flexi UI.
@@ -48,6 +52,85 @@ data class Sizes(
val borderSizeTertiary: Dp val borderSizeTertiary: Dp
) )
/**
* Descriptor for [Sizes].
*/
@ExperimentalFlexiUISizesApi
@Stable
internal enum class SizesDescriptor {
SpacingPrimary,
SpacingSecondary,
SpacingTertiary,
IconSizePrimary,
IconSizeSecondary,
IconSizeTertiary,
ZoomSizePrimary,
ZoomSizeSecondary,
ZoomSizeTertiary,
BorderSizePrimary,
BorderSizeSecondary,
BorderSizeTertiary
}
/**
* Descriptor for [ComponentPadding].
*/
@Stable
internal fun PaddingDescriptor(
all: SizesDescriptor
): PaddingDescriptor = PaddingDescriptorImpl(all, all, all, all)
/**
* Descriptor for [ComponentPadding].
*/
@Stable
internal fun PaddingDescriptor(
horizontal: SizesDescriptor? = null,
vertical: SizesDescriptor? = null
): PaddingDescriptor = PaddingDescriptorImpl(horizontal, vertical, vertical, horizontal)
/**
* Descriptor for [ComponentPadding].
*/
@Stable
internal fun PaddingDescriptor(
start: SizesDescriptor? = null,
top: SizesDescriptor? = null,
bottom: SizesDescriptor? = null,
end: SizesDescriptor? = null
): PaddingDescriptor = PaddingDescriptorImpl(start, top, bottom, end)
@Stable
internal interface PaddingDescriptor {
val start: SizesDescriptor?
val top: SizesDescriptor?
val bottom: SizesDescriptor?
val end: SizesDescriptor?
@Composable
@ReadOnlyComposable
fun toPadding(): ComponentPadding
}
@Immutable
private class PaddingDescriptorImpl(
override val start: SizesDescriptor?,
override val top: SizesDescriptor?,
override val bottom: SizesDescriptor?,
override val end: SizesDescriptor?
) : PaddingDescriptor {
@Composable
@ReadOnlyComposable
override fun toPadding() = ComponentPadding(
start = start?.toDp() ?: 0.dp,
top = top?.toDp() ?: 0.dp,
bottom = bottom?.toDp() ?: 0.dp,
end = end?.toDp() ?: 0.dp
)
}
internal val LocalSizes = staticCompositionLocalOf { DefaultSizes } internal val LocalSizes = staticCompositionLocalOf { DefaultSizes }
internal val DefaultSizes = Sizes( internal val DefaultSizes = Sizes(
@@ -65,6 +148,36 @@ internal val DefaultSizes = Sizes(
borderSizeTertiary = 0.dp borderSizeTertiary = 0.dp
) )
/**
* Gets a [Dp] from descriptor.
* @see SizesDescriptor.toDp
* @param value the descriptor.
* @return [Dp]
*/
internal fun Sizes.fromDescriptor(value: SizesDescriptor) = when (value) {
SizesDescriptor.SpacingPrimary -> spacingPrimary
SizesDescriptor.SpacingSecondary -> spacingSecondary
SizesDescriptor.SpacingTertiary -> spacingTertiary
SizesDescriptor.IconSizePrimary -> iconSizePrimary
SizesDescriptor.IconSizeSecondary -> iconSizeSecondary
SizesDescriptor.IconSizeTertiary -> iconSizeTertiary
SizesDescriptor.ZoomSizePrimary -> zoomSizePrimary
SizesDescriptor.ZoomSizeSecondary -> zoomSizeSecondary
SizesDescriptor.ZoomSizeTertiary -> zoomSizeTertiary
SizesDescriptor.BorderSizePrimary -> borderSizePrimary
SizesDescriptor.BorderSizeSecondary -> borderSizeSecondary
SizesDescriptor.BorderSizeTertiary -> borderSizeTertiary
}
/**
* Converts a descriptor to a [Dp].
* @see Sizes.fromDescriptor
* @return [Dp]
*/
@Composable
@ReadOnlyComposable
internal fun SizesDescriptor.toDp() = LocalSizes.current.fromDescriptor(this)
/** /**
* The [Sizes] is experimental, the relevant design specifications for size are still being improved, * The [Sizes] is experimental, the relevant design specifications for size are still being improved,
* this is the old design plan. * this is the old design plan.

View File

@@ -23,7 +23,10 @@
package com.highcapable.flexiui package com.highcapable.flexiui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -42,6 +45,18 @@ data class Typography(
val secondary: TextStyle val secondary: TextStyle
) )
/**
* Descriptor for [Typography].
*/
@Stable
internal enum class TypographyDescriptor {
TitlePrimary,
TitleSecondary,
Subtitle,
Primary,
Secondary
}
internal val LocalTypography = staticCompositionLocalOf { DefaultTypography } internal val LocalTypography = staticCompositionLocalOf { DefaultTypography }
private val DefaultLineHeight = 1.5.em private val DefaultLineHeight = 1.5.em
@@ -70,3 +85,26 @@ internal val DefaultTypography = Typography(
lineHeight = DefaultLineHeight lineHeight = DefaultLineHeight
) )
) )
/**
* Gets a [TextStyle] from descriptor.
* @see TypographyDescriptor.toTextStyle
* @param value the descriptor.
* @return [TextStyle]
*/
internal fun Typography.fromDescriptor(value: TypographyDescriptor) = when (value) {
TypographyDescriptor.TitlePrimary -> titlePrimary
TypographyDescriptor.TitleSecondary -> titleSecondary
TypographyDescriptor.Subtitle -> subtitle
TypographyDescriptor.Primary -> primary
TypographyDescriptor.Secondary -> secondary
}
/**
* Converts a descriptor to a [TextStyle].
* @see Typography.fromDescriptor
* @return [TextStyle]
*/
@Composable
@ReadOnlyComposable
internal fun TypographyDescriptor.toTextStyle() = LocalTypography.current.fromDescriptor(this)

View File

@@ -38,9 +38,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -49,18 +47,20 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalTypography import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.resources.FlexiIcons import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.ArrowNaviUp import com.highcapable.flexiui.resources.icon.ArrowNaviUp
import com.highcapable.flexiui.resources.icon.FinishClose import com.highcapable.flexiui.resources.icon.FinishClose
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toTextStyle
/** /**
* Colors defines for app bar. * Colors defines for app bar.
* @param titleTextColor the title text color. * @see AppBarDefaults.colors
* @param subTextColor the sub text color.
* @param actionContentColor the action content color, usually for icon tint and text color.
*/ */
@Immutable @Immutable
data class AppBarColors( data class AppBarColors(
@@ -71,13 +71,7 @@ data class AppBarColors(
/** /**
* Style defines for app bar. * Style defines for app bar.
* @param padding the padding of content. * @see AppBarDefaults.style
* @param contentSpacing the spacing between the components of content.
* @param titleTextStyle the title text style.
* @param subTextStyle the sub text style.
* @param actionIconSize the size of action icon.
* @param actionIconPadding the padding of action icon.
* @param actionContentMaxWidth the max width of actions content.
*/ */
@Immutable @Immutable
data class AppBarStyle( data class AppBarStyle(
@@ -103,8 +97,8 @@ data class AppBarStyle(
@Composable @Composable
fun PrimaryAppBar( fun PrimaryAppBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: AppBarColors? = null, colors: AppBarColors = AppBarDefaults.colors(),
style: AppBarStyle? = null, style: AppBarStyle = AppBarDefaults.style(type = AppBarType.Primary),
titleText: @Composable () -> Unit, titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null, subText: @Composable (() -> Unit)? = null,
actions: @Composable (AppBarScope.() -> Unit)? = null actions: @Composable (AppBarScope.() -> Unit)? = null
@@ -137,8 +131,8 @@ fun PrimaryAppBar(
@Composable @Composable
fun SecondaryAppBar( fun SecondaryAppBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: AppBarColors? = null, colors: AppBarColors = AppBarDefaults.colors(),
style: AppBarStyle? = null, style: AppBarStyle = AppBarDefaults.style(type = AppBarType.Secondary),
titleText: @Composable () -> Unit, titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null, subText: @Composable (() -> Unit)? = null,
finishIcon: @Composable (AppBarScope.() -> Unit)? = null, finishIcon: @Composable (AppBarScope.() -> Unit)? = null,
@@ -165,22 +159,19 @@ fun SecondaryAppBar(
private fun BasicAppBar( private fun BasicAppBar(
type: AppBarType, type: AppBarType,
modifier: Modifier, modifier: Modifier,
colors: AppBarColors?, colors: AppBarColors,
style: AppBarStyle?, style: AppBarStyle,
titleText: @Composable () -> Unit, titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)?, subText: @Composable (() -> Unit)?,
finishIcon: @Composable (AppBarScope.() -> Unit)?, finishIcon: @Composable (AppBarScope.() -> Unit)?,
navigationIcon: @Composable (AppBarScope.() -> Unit)?, navigationIcon: @Composable (AppBarScope.() -> Unit)?,
actions: @Composable (AppBarScope.() -> Unit)? actions: @Composable (AppBarScope.() -> Unit)?
) { ) {
CompositionLocalProvider(LocalAppBarType provides type) { Box(modifier = modifier.padding(style.padding)) {
val currentColors = colors ?: AppBarDefaults.colors
val currentStyle = style ?: AppBarDefaults.style
Box(modifier = modifier.padding(currentStyle.padding)) {
AppBarImpl( AppBarImpl(
type = type, type = type,
colors = currentColors, colors = colors,
style = currentStyle, style = style,
titleText = titleText, titleText = titleText,
subText = subText, subText = subText,
finishIcon = finishIcon, finishIcon = finishIcon,
@@ -189,7 +180,6 @@ private fun BasicAppBar(
).Content() ).Content()
} }
} }
}
/** /**
* A scope for app bar. * A scope for app bar.
@@ -210,8 +200,8 @@ interface AppBarScope {
fun FinishIconButton( fun FinishIconButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors, colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style, style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) { ) {
@@ -238,8 +228,8 @@ interface AppBarScope {
fun NavigationIconButton( fun NavigationIconButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors, colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style, style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) { ) {
@@ -267,8 +257,8 @@ interface AppBarScope {
fun ActionIconButton( fun ActionIconButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors, colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style, style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit content: @Composable () -> Unit
@@ -394,55 +384,99 @@ private class AppBarImpl(
@Stable @Stable
private val AppBarScope.impl get() = this as? AppBarImpl? ?: error("Could not got AppBarScope's impl.") private val AppBarScope.impl get() = this as? AppBarImpl? ?: error("Could not got AppBarScope's impl.")
/**
* App bar's type definition.
*/
@Stable @Stable
private enum class AppBarType { Primary, Secondary } enum class AppBarType {
/** @see PrimaryAppBar */
Primary,
/** @see SecondaryAppBar */
Secondary
}
/** /**
* Defaults of app bar. * Defaults of app bar.
*/ */
object AppBarDefaults { object AppBarDefaults {
val colors: AppBarColors
/**
* Creates a [AppBarColors] with the default values.
* @param titleTextColor the title text color.
* @param subTextColor the sub text color.
* @param actionContentColor the action content color, usually for icon tint and text color.
* @return [AppBarColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultAppBarColors() titleTextColor: Color = AppBarProperties.TitleTextColor.toColor(),
val style: AppBarStyle subTextColor: Color = AppBarProperties.SubTextColor.toColor(),
actionContentColor: Color = AppBarProperties.ActionContentColor.toColor()
) = AppBarColors(
titleTextColor = titleTextColor,
subTextColor = subTextColor,
actionContentColor = actionContentColor
)
/**
* Creates a [AppBarStyle] with the default values.
* @param type the type of app bar.
* @param padding the padding of content.
* @param contentSpacing the spacing between the components of content.
* @param titleTextStyle the title text style.
* @param subTextStyle the sub text style.
* @param actionIconSize the size of action icon.
* @param actionIconPadding the padding of action icon.
* @param actionContentMaxWidth the max width of actions content.
* @return [AppBarStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultAppBarStyle() type: AppBarType,
padding: ComponentPadding = when {
LocalInSurface.current || LocalInAreaBox.current ->
AppBarProperties.InBoxPadding
else -> AppBarProperties.Padding
}.toPadding(),
contentSpacing: Dp = AppBarProperties.ContentSpacing.toDp(),
titleTextStyle: TextStyle = when (type) {
AppBarType.Primary -> AppBarProperties.PrimaryTitleTextStyle
AppBarType.Secondary -> AppBarProperties.SecondaryTitleTextStyle
}.toTextStyle(),
subTextStyle: TextStyle = AppBarProperties.SubTextStyle.toTextStyle(),
actionIconSize: Dp = when (type) {
AppBarType.Primary -> AppBarProperties.PrimaryActionIconSize
AppBarType.Secondary -> AppBarProperties.SecondaryActionIconSize
}.toDp(),
actionIconPadding: Dp = AppBarProperties.ActionIconPadding.toDp(),
actionContentMaxWidth: Dp = AppBarProperties.ActionContentMaxWidth
) = AppBarStyle(
padding = padding,
contentSpacing = contentSpacing,
titleTextStyle = titleTextStyle,
subTextStyle = subTextStyle,
actionIconSize = actionIconSize,
actionIconPadding = actionIconPadding,
actionContentMaxWidth = actionContentMaxWidth
)
} }
private val LocalAppBarType = compositionLocalOf { AppBarType.Primary } @Stable
internal object AppBarProperties {
@Composable val TitleTextColor = ColorsDescriptor.TextPrimary
@ReadOnlyComposable val SubTextColor = ColorsDescriptor.TextSecondary
private fun defaultAppBarColors() = AppBarColors( val ActionContentColor = ColorsDescriptor.TextPrimary
titleTextColor = LocalColors.current.textPrimary, val Padding = PaddingDescriptor(SizesDescriptor.SpacingPrimary)
subTextColor = LocalColors.current.textSecondary, val InBoxPadding = PaddingDescriptor(vertical = SizesDescriptor.SpacingPrimary)
actionContentColor = LocalColors.current.textPrimary val ContentSpacing = SizesDescriptor.SpacingSecondary
) val PrimaryTitleTextStyle = TypographyDescriptor.TitlePrimary
val SecondaryTitleTextStyle = TypographyDescriptor.TitleSecondary
@Composable val SubTextStyle = TypographyDescriptor.Subtitle
@ReadOnlyComposable val PrimaryActionIconSize = SizesDescriptor.IconSizePrimary
private fun defaultAppBarStyle() = AppBarStyle( val SecondaryActionIconSize = SizesDescriptor.IconSizeSecondary
padding = when { val ActionIconPadding = SizesDescriptor.SpacingTertiary
LocalInSurface.current || LocalInAreaBox.current -> val ActionContentMaxWidth = 170.dp
ComponentPadding(vertical = LocalSizes.current.spacingPrimary) }
else -> ComponentPadding(LocalSizes.current.spacingPrimary)
},
contentSpacing = LocalSizes.current.spacingSecondary,
titleTextStyle = when (LocalAppBarType.current) {
AppBarType.Primary -> LocalTypography.current.titlePrimary
AppBarType.Secondary -> LocalTypography.current.titleSecondary
},
subTextStyle = LocalTypography.current.subtitle,
actionIconSize = when (LocalAppBarType.current) {
AppBarType.Primary -> LocalSizes.current.iconSizePrimary
AppBarType.Secondary -> LocalSizes.current.iconSizeSecondary
},
actionIconPadding = LocalSizes.current.spacingTertiary,
actionContentMaxWidth = DefaultActionContentMaxWidth
)
private val DefaultActionContentMaxWidth = 170.dp
private const val VerticalContentSpacingRatio = 1.6f private const val VerticalContentSpacingRatio = 1.6f

View File

@@ -23,7 +23,6 @@
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -36,7 +35,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -50,23 +49,33 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.flexiui.DefaultShapes import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
/**
* Colors defines for area box.
* @see AreaBoxDefaults.colors
*/
@Immutable
data class AreaBoxColors(
val backgroundColor: Color,
val borderColor: Color
)
/** /**
* Style defines for area box. * Style defines for area box.
* @param padding the padding of content. * @see AreaBoxDefaults.style
* @param shape the shape of the box.
* @param border the border stroke of the box.
* @param shadowSize the shadow size of the box.
*/ */
@Immutable @Immutable
data class AreaBoxStyle( data class AreaBoxStyle(
val padding: ComponentPadding, val padding: ComponentPadding,
val shape: Shape, val shape: Shape,
val border: BorderStroke, val borderWidth: Dp,
val shadowSize: Dp val shadowSize: Dp
) )
@@ -76,7 +85,7 @@ data class AreaBoxStyle(
* @see AreaColumn * @see AreaColumn
* @param modifier the [Modifier] to be applied to this area box. * @param modifier the [Modifier] to be applied to this area box.
* @param initializer the [Modifier] initializer, earlies than [modifier]. * @param initializer the [Modifier] initializer, earlies than [modifier].
* @param color the background color of this area box, default is [AreaBoxDefaults.color]. * @param colors the colors of this area box, default is [AreaBoxDefaults.colors].
* @param style the style of this area box, default is [AreaBoxDefaults.style]. * @param style the style of this area box, default is [AreaBoxDefaults.style].
* @param contentAlignment the alignment of the content inside this area box, default is [Alignment.TopStart]. * @param contentAlignment the alignment of the content inside this area box, default is [Alignment.TopStart].
* @param propagateMinConstraints whether to propagate the min constraints from the content to this area box. * @param propagateMinConstraints whether to propagate the min constraints from the content to this area box.
@@ -86,8 +95,8 @@ data class AreaBoxStyle(
fun AreaBox( fun AreaBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
initializer: @Composable Modifier.() -> Modifier = { Modifier }, initializer: @Composable Modifier.() -> Modifier = { Modifier },
color: Color = AreaBoxDefaults.color, colors: AreaBoxColors = AreaBoxDefaults.colors(),
style: AreaBoxStyle = AreaBoxDefaults.style, style: AreaBoxStyle = AreaBoxDefaults.style(),
contentAlignment: Alignment = Alignment.TopStart, contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false, propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit content: @Composable BoxScope.() -> Unit
@@ -97,7 +106,7 @@ fun AreaBox(
LocalAreaBoxShape provides style.shape LocalAreaBoxShape provides style.shape
) { ) {
Box( Box(
modifier = Modifier.areaBox(color, style, modifier, initializer), modifier = Modifier.areaBox(colors, style, modifier, initializer),
contentAlignment = contentAlignment, contentAlignment = contentAlignment,
propagateMinConstraints = propagateMinConstraints, propagateMinConstraints = propagateMinConstraints,
content = content content = content
@@ -111,7 +120,7 @@ fun AreaBox(
* @see AreaBox * @see AreaBox
* @param modifier the [Modifier] to be applied to this area row. * @param modifier the [Modifier] to be applied to this area row.
* @param initializer the [Modifier] initializer, earlies than [modifier]. * @param initializer the [Modifier] initializer, earlies than [modifier].
* @param color the background color of this area row, default is [AreaBoxDefaults.color]. * @param colors the colors of this area row, default is [AreaBoxDefaults.colors].
* @param style the style of this area row, default is [AreaBoxDefaults.style]. * @param style the style of this area row, default is [AreaBoxDefaults.style].
* @param horizontalArrangement the horizontal arrangement of the content inside this area row, default is [Arrangement.Start]. * @param horizontalArrangement the horizontal arrangement of the content inside this area row, default is [Arrangement.Start].
* @param verticalAlignment the vertical alignment of the content inside this area row, default is [Alignment.Top]. * @param verticalAlignment the vertical alignment of the content inside this area row, default is [Alignment.Top].
@@ -121,8 +130,8 @@ fun AreaBox(
fun AreaRow( fun AreaRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
initializer: @Composable Modifier.() -> Modifier = { Modifier }, initializer: @Composable Modifier.() -> Modifier = { Modifier },
color: Color = AreaBoxDefaults.color, colors: AreaBoxColors = AreaBoxDefaults.colors(),
style: AreaBoxStyle = AreaBoxDefaults.style, style: AreaBoxStyle = AreaBoxDefaults.style(),
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top, verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit content: @Composable RowScope.() -> Unit
@@ -132,7 +141,7 @@ fun AreaRow(
LocalAreaBoxShape provides style.shape LocalAreaBoxShape provides style.shape
) { ) {
Row( Row(
modifier = Modifier.areaBox(color, style, modifier, initializer), modifier = Modifier.areaBox(colors, style, modifier, initializer),
horizontalArrangement = horizontalArrangement, horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment, verticalAlignment = verticalAlignment,
content = content content = content
@@ -146,7 +155,7 @@ fun AreaRow(
* @see AreaBox * @see AreaBox
* @param modifier the [Modifier] to be applied to this area column. * @param modifier the [Modifier] to be applied to this area column.
* @param initializer the [Modifier] initializer, earlies than [modifier]. * @param initializer the [Modifier] initializer, earlies than [modifier].
* @param color the background color of this area column, default is [AreaBoxDefaults.color]. * @param colors the colors of this area column, default is [AreaBoxDefaults.colors].
* @param style the style of this area column, default is [AreaBoxDefaults.style]. * @param style the style of this area column, default is [AreaBoxDefaults.style].
* @param verticalArrangement the vertical arrangement of the content inside this area column, default is [Arrangement.Top]. * @param verticalArrangement the vertical arrangement of the content inside this area column, default is [Arrangement.Top].
* @param horizontalAlignment the horizontal alignment of the content inside this area column, default is [Alignment.Start]. * @param horizontalAlignment the horizontal alignment of the content inside this area column, default is [Alignment.Start].
@@ -156,8 +165,8 @@ fun AreaRow(
fun AreaColumn( fun AreaColumn(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
initializer: @Composable Modifier.() -> Modifier = { Modifier }, initializer: @Composable Modifier.() -> Modifier = { Modifier },
color: Color = AreaBoxDefaults.color, colors: AreaBoxColors = AreaBoxDefaults.colors(),
style: AreaBoxStyle = AreaBoxDefaults.style, style: AreaBoxStyle = AreaBoxDefaults.style(),
verticalArrangement: Arrangement.Vertical = Arrangement.Top, verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start, horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
@@ -167,7 +176,7 @@ fun AreaColumn(
LocalAreaBoxShape provides style.shape LocalAreaBoxShape provides style.shape
) { ) {
Column( Column(
modifier = Modifier.areaBox(color, style, modifier, initializer), modifier = Modifier.areaBox(colors, style, modifier, initializer),
verticalArrangement = verticalArrangement, verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment, horizontalAlignment = horizontalAlignment,
content = content content = content
@@ -176,22 +185,22 @@ fun AreaColumn(
} }
private fun Modifier.areaBox( private fun Modifier.areaBox(
color: Color, colors: AreaBoxColors,
style: AreaBoxStyle, style: AreaBoxStyle,
then: Modifier, then: Modifier,
initializer: @Composable Modifier.() -> Modifier initializer: @Composable Modifier.() -> Modifier
) = composed( ) = composed(
inspectorInfo = debugInspectorInfo { inspectorInfo = debugInspectorInfo {
name = "areaBox" name = "areaBox"
properties["color"] = color properties["colors"] = colors
properties["style"] = style properties["style"] = style
} }
) { ) {
initializer() initializer()
.shadow(style.shadowSize, style.shape) .shadow(style.shadowSize, style.shape)
.clip(style.shape) .clip(style.shape)
.background(color, style.shape) .background(colors.backgroundColor, style.shape)
.borderOrElse(style.border, style.shape) .borderOrElse(style.borderWidth, colors.borderColor, style.shape)
.then(then) .then(then)
.padding(style.padding) .padding(style.padding)
} }
@@ -200,47 +209,73 @@ private fun Modifier.areaBox(
* Defaults of area box. * Defaults of area box.
*/ */
object AreaBoxDefaults { object AreaBoxDefaults {
val color: Color
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxColor()
val style: AreaBoxStyle
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxStyle()
}
internal val LocalInAreaBox = compositionLocalOf { false }
internal val LocalAreaBoxShape = compositionLocalOf { DefaultAreaBoxShape }
/**
* Creates a [AreaBoxColors] with the default values.
* @param backgroundColor the background color of the box.
* @param borderColor the border color of the box.
* @return [AreaBoxColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
internal fun withAreaBoxShape( backgroundColor: Color = AreaBoxProperties.BackgroundColor.toColor(),
inBox: Shape = LocalAreaBoxShape.current, borderColor: Color = AreaBoxProperties.BorderColor.toColor()
outBox: Shape = LocalShapes.current.secondary ) = AreaBoxColors(
backgroundColor = backgroundColor,
borderColor = borderColor
)
/**
* Creates a [AreaBoxStyle] with the default values.
* @param padding the padding of content.
* @param shape the shape of the box.
* @param borderWidth the border width of the box.
* @param shadowSize the shadow size of the box.
* @return [AreaBoxStyle]
*/
@Composable
fun style(
padding: ComponentPadding = AreaBoxProperties.Padding.toPadding(),
shape: Shape = AreaBoxProperties.Shape.toShape(),
borderWidth: Dp = AreaBoxProperties.BorderWidth.toDp(),
shadowSize: Dp = AreaBoxProperties.ShadowSize
) = AreaBoxStyle(
padding = padding,
shape = shape,
borderWidth = borderWidth,
shadowSize = shadowSize
)
/**
* Returns the child components shape of the current area box.
*
* Design specification: The shape of the components inside the area box
* should be consistent with the shape of the area box.
* @param inBox the shape of inner area box.
* @param outBox the shape of outside area box.
* @return [Shape]
*/
@Composable
fun childShape(
inBox: Shape = LocalAreaBoxShape.current ?: AreaBoxProperties.Shape.toShape(),
outBox: Shape = AreaBoxProperties.OutBoxShape.toShape()
) = when (LocalInAreaBox.current) { ) = when (LocalInAreaBox.current) {
true -> inBox true -> inBox
else -> outBox else -> outBox
} }
}
@Composable @Stable
@ReadOnlyComposable internal object AreaBoxProperties {
private fun defaultAreaBoxStyle() = AreaBoxStyle( val BackgroundColor = ColorsDescriptor.ForegroundPrimary
padding = ComponentPadding(LocalSizes.current.spacingPrimary), val BorderColor = ColorsDescriptor.TextPrimary
shape = LocalAreaBoxShape.current, val Padding = PaddingDescriptor(SizesDescriptor.SpacingPrimary)
border = defaultAreaBoxBorder(), val Shape = ShapesDescriptor.Primary
shadowSize = DefaultAreaBoxShadowSize val OutBoxShape = ShapesDescriptor.Secondary
) val BorderWidth = SizesDescriptor.BorderSizeTertiary
val ShadowSize = 0.dp
}
@Composable internal val LocalInAreaBox = compositionLocalOf { false }
@ReadOnlyComposable
private fun defaultAreaBoxColor() = LocalColors.current.foregroundPrimary
@Composable internal val LocalAreaBoxShape = compositionLocalOf<Shape?> { null }
@ReadOnlyComposable
private fun defaultAreaBoxBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
private val DefaultAreaBoxShape: Shape = DefaultShapes.primary
private val DefaultAreaBoxShadowSize = 0.dp

View File

@@ -19,11 +19,10 @@
* *
* This file is created by fankes on 2023/11/5. * This file is created by fankes on 2023/11/5.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -33,7 +32,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -44,42 +43,46 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.InteractionDefaults import com.highcapable.flexiui.component.interaction.InteractionDefaults
import com.highcapable.flexiui.component.interaction.RippleStyle import com.highcapable.flexiui.component.interaction.RippleStyle
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.component.interaction.rippleToggleable import com.highcapable.flexiui.component.interaction.rippleToggleable
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
/** /**
* Colors defines for button. * Colors defines for button.
* @param contentColor the content color, usually for icon tint and text color. * @see ButtonDefaults.colors
* @param backgroundColor the background color. * @see IconButtonDefaults.colors
*/ */
@Immutable @Immutable
data class ButtonColors( data class ButtonColors(
val contentColor: Color, val contentColor: Color,
val backgroundColor: Color val backgroundColor: Color,
val borderColor: Color
) )
/** /**
* Style defines for button. * Style defines for button.
* @param rippleStyle the ripple style of this button. * @see ButtonDefaults.style
* @param padding the padding of content. * @see IconButtonDefaults.style
* @param shape the shape.
* @param border the border stroke.
*/ */
@Immutable @Immutable
data class ButtonStyle( data class ButtonStyle(
val rippleStyle: RippleStyle, val rippleStyle: RippleStyle,
val padding: ComponentPadding, val padding: ComponentPadding,
val shape: Shape, val shape: Shape,
val border: BorderStroke val borderWidth: Dp
) )
/** /**
@@ -100,8 +103,8 @@ data class ButtonStyle(
fun Button( fun Button(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.colors, colors: ButtonColors = ButtonDefaults.colors(),
style: ButtonStyle = ButtonDefaults.style, style: ButtonStyle = ButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
header: @Composable () -> Unit = {}, header: @Composable () -> Unit = {},
@@ -109,7 +112,7 @@ fun Button(
content: @Composable RowScope.() -> Unit content: @Composable RowScope.() -> Unit
) { ) {
val localTextStyle = LocalTextStyle.current.copy(color = colors.contentColor) val localTextStyle = LocalTextStyle.current.copy(color = colors.contentColor)
val localProgressIndicatorColors = LocalProgressIndicatorColors.current.copy( val localProgressIndicatorColors = LocalProgressIndicatorColors.current?.copy(
foregroundColor = colors.contentColor, foregroundColor = colors.contentColor,
backgroundColor = Color.Transparent backgroundColor = Color.Transparent
) )
@@ -161,8 +164,8 @@ fun Button(
fun IconButton( fun IconButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors, colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style, style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit content: @Composable () -> Unit
@@ -181,12 +184,7 @@ fun IconButton(
onClick = onClick onClick = onClick
).padding(style.padding), ).padding(style.padding),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) { IconButtonStyle(colors, content) }
CompositionLocalProvider(
LocalIconStyle provides LocalIconStyle.current.copy(tint = colors.contentColor),
content = content
)
}
} }
/** /**
@@ -207,8 +205,8 @@ fun IconToggleButton(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors, colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style, style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit content: @Composable () -> Unit
@@ -228,13 +226,22 @@ fun IconToggleButton(
interactionSource = interactionSource interactionSource = interactionSource
).padding(style.padding), ).padding(style.padding),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { IconButtonStyle(colors, content) }
}
@Composable
private fun IconButtonStyle(
colors: ButtonColors,
content: @Composable () -> Unit
) { ) {
// The provided tint should have the highest priority,
// this will allow the user to override the current style through [LocalIconStyle].
val iconStyle = LocalIconStyle.current.let { it.copy(tint = it.tint.orNull() ?: colors.contentColor) }
CompositionLocalProvider( CompositionLocalProvider(
LocalIconStyle provides LocalIconStyle.current.copy(tint = colors.contentColor), LocalIconStyle provides iconStyle,
content = content content = content
) )
} }
}
private fun Modifier.button( private fun Modifier.button(
enabled: Boolean, enabled: Boolean,
@@ -252,7 +259,7 @@ private fun Modifier.button(
componentState(enabled) componentState(enabled)
.clip(style.shape) .clip(style.shape)
.background(colors.backgroundColor, style.shape) .background(colors.backgroundColor, style.shape)
.borderOrElse(style.border, style.shape) .borderOrElse(style.borderWidth, colors.borderColor, style.shape)
.then(then) .then(then)
} }
@@ -260,104 +267,127 @@ private fun Modifier.button(
* Defaults of button. * Defaults of button.
*/ */
object ButtonDefaults { object ButtonDefaults {
val colors: ButtonColors
/**
* Creates a [ButtonColors] with the default values.
* @param contentColor the content color, usually for icon tint and text color.
* @param backgroundColor the background color.
* @param borderColor the border color.
* @return [ButtonColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultButtonColors() contentColor: Color = when {
val style: ButtonStyle LocalPrimaryButton.current -> ButtonProperties.PrimaryContentColor
LocalInAreaBox.current -> ButtonProperties.ContentColor.toColor()
else -> ButtonProperties.PrimaryContentColor
},
backgroundColor: Color = when {
LocalPrimaryButton.current -> ButtonProperties.PrimaryBackgroundColor
LocalInAreaBox.current -> ButtonProperties.BackgroundColor
else -> ButtonProperties.PrimaryBackgroundColor
}.toColor(),
borderColor: Color = ButtonProperties.BorderColor.toColor()
) = ButtonColors(
contentColor = contentColor,
backgroundColor = backgroundColor,
borderColor = borderColor
)
/**
* Creates a [ButtonStyle] with the default values.
* @param rippleStyle the ripple style of this button.
* @param padding the padding of content.
* @param shape the shape.
* @param borderWidth the border width.
* @return [ButtonStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultButtonStyle() rippleStyle: RippleStyle = InteractionDefaults.rippleStyle(color = when {
LocalPrimaryButton.current -> ButtonProperties.PrimaryRippleColor
LocalInAreaBox.current -> ButtonProperties.RippleColor
else -> ButtonProperties.PrimaryRippleColor
}.toColor()),
padding: ComponentPadding = ButtonProperties.Padding.toPadding(),
shape: Shape = AreaBoxDefaults.childShape(),
borderWidth: Dp = ButtonProperties.BorderWidth.toDp()
) = ButtonStyle(
rippleStyle = rippleStyle,
padding = padding,
shape = shape,
borderWidth = borderWidth
)
} }
/** /**
* Defaults of icon button. * Defaults of icon button.
*/ */
object IconButtonDefaults { object IconButtonDefaults {
val colors: ButtonColors
/**
* Creates a [ButtonColors] with the default values.
* @param contentColor the content color, usually for icon tint and text color.
* @param backgroundColor the background color.
* @param borderColor the border color.
* @return [ButtonColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultIconButtonColors() contentColor: Color = IconButtonProperties.ContentColor.toColor(),
val style: ButtonStyle backgroundColor: Color = IconButtonProperties.BackgroundColor,
borderColor: Color = ButtonProperties.BorderColor.toColor()
) = ButtonColors(
contentColor = contentColor,
backgroundColor = backgroundColor,
borderColor = borderColor
)
/**
* Creates a [ButtonStyle] with the default values.
* @param rippleStyle the ripple style of this button.
* @param padding the padding of content.
* @param shape the shape.
* @param borderWidth the border width.
* @return [ButtonStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultIconButtonStyle() rippleStyle: RippleStyle = InteractionDefaults.rippleStyle(bounded = IconButtonProperties.RippleBounded),
padding: ComponentPadding = IconButtonProperties.Padding,
shape: Shape = IconButtonProperties.Shape.toShape(),
borderWidth: Dp = ButtonProperties.BorderWidth.toDp()
) = ButtonStyle(
rippleStyle = rippleStyle,
padding = padding,
shape = shape,
borderWidth = borderWidth
)
}
@Stable
internal object ButtonProperties {
val PrimaryContentColor = Color.White
val ContentColor = ColorsDescriptor.TextPrimary
val PrimaryBackgroundColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = ColorsDescriptor.ForegroundSecondary
val BorderColor = ColorsDescriptor.TextPrimary
val PrimaryRippleColor = ColorsDescriptor.ForegroundSecondary
val RippleColor = ColorsDescriptor.ThemeSecondary
val Padding = PaddingDescriptor(
horizontal = SizesDescriptor.SpacingPrimary,
vertical = SizesDescriptor.SpacingSecondary
)
val BorderWidth = SizesDescriptor.BorderSizeTertiary
}
@Stable
internal object IconButtonProperties {
val ContentColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = Color.Transparent
const val RippleBounded = false
val Padding = ComponentPadding()
val Shape = ShapesDescriptor.Tertiary
} }
internal val LocalPrimaryButton = compositionLocalOf { false } internal val LocalPrimaryButton = compositionLocalOf { false }
@Composable
@ReadOnlyComposable
private fun defaultPrimaryButtonColors() = ButtonColors(
contentColor = Color.White,
backgroundColor = LocalColors.current.themePrimary
)
@Composable
@ReadOnlyComposable
private fun defaultContentButtonColors() = ButtonColors(
contentColor = LocalColors.current.textPrimary,
backgroundColor = LocalColors.current.foregroundSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultPrimaryButtonRippleStyle() =
InteractionDefaults.rippleStyle.copy(color = LocalColors.current.foregroundSecondary)
@Composable
@ReadOnlyComposable
private fun defaultContentButtonRippleStyle() =
InteractionDefaults.rippleStyle.copy(color = LocalColors.current.themeSecondary)
@Composable
@ReadOnlyComposable
private fun defaultIconButtonRippleStyle() = InteractionDefaults.rippleStyle.copy(bounded = false)
@Composable
@ReadOnlyComposable
private fun defaultButtonColors() = when {
LocalPrimaryButton.current -> defaultPrimaryButtonColors()
LocalInAreaBox.current -> defaultContentButtonColors()
else -> defaultPrimaryButtonColors()
}
@Composable
@ReadOnlyComposable
private fun defaultButtonStyle() = ButtonStyle(
rippleStyle = defaultButtonRippleStyle(),
padding = ComponentPadding(
horizontal = LocalSizes.current.spacingPrimary,
vertical = LocalSizes.current.spacingSecondary
),
shape = withAreaBoxShape(),
border = defaultButtonBorder()
)
@Composable
@ReadOnlyComposable
private fun defaultButtonRippleStyle() = when {
LocalPrimaryButton.current -> defaultPrimaryButtonRippleStyle()
LocalInAreaBox.current -> defaultContentButtonRippleStyle()
else -> defaultPrimaryButtonRippleStyle()
}
@Composable
@ReadOnlyComposable
private fun defaultIconButtonColors() = ButtonColors(
contentColor = LocalIconStyle.current.tint.orNull() ?: LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultIconButtonStyle() = ButtonStyle(
rippleStyle = defaultIconButtonRippleStyle(),
padding = ComponentPadding(),
shape = LocalShapes.current.tertiary,
border = defaultButtonBorder()
)
@Composable
@ReadOnlyComposable
private fun defaultButtonBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)

View File

@@ -19,13 +19,12 @@
* *
* This file is created by fankes on 2023/11/9. * This file is created by fankes on 2023/11/9.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.interaction.collectIsHoveredAsState
@@ -39,7 +38,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -55,33 +54,28 @@ import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.clickable import com.highcapable.betterandroid.compose.extension.ui.clickable
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.resources.FlexiIcons import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.CheckMark import com.highcapable.flexiui.resources.icon.CheckMark
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
/** /**
* Colors defines for check box. * Colors defines for check box.
* @param contentColor the color of the check mark. * @see CheckBoxDefaults.colors
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
*/ */
@Immutable @Immutable
data class CheckBoxColors( data class CheckBoxColors(
val contentColor: Color, val contentColor: Color,
val inactiveColor: Color, val inactiveColor: Color,
val activeColor: Color val activeColor: Color,
val borderColor: Color
) )
/** /**
* Style defines for check box. * Style defines for check box.
* @param contentSpacing the spacing between the check mark and the content. * @see CheckBoxDefaults.style
* @param contentSize the size of the check mark.
* @param strokeSize the stroke size.
* @param pressedGain the gain when pressed.
* @param hoveredGain the gain when hovered.
* @param shape the shape.
* @param border the border stroke.
*/ */
@Immutable @Immutable
data class CheckBoxStyle( data class CheckBoxStyle(
@@ -91,7 +85,7 @@ data class CheckBoxStyle(
val pressedGain: Float, val pressedGain: Float,
val hoveredGain: Float, val hoveredGain: Float,
val shape: Shape, val shape: Shape,
val border: BorderStroke val borderWidth: Dp
) )
/** /**
@@ -110,8 +104,8 @@ fun CheckBox(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: CheckBoxColors = CheckBoxDefaults.colors, colors: CheckBoxColors = CheckBoxDefaults.colors(),
style: CheckBoxStyle = CheckBoxDefaults.style, style: CheckBoxStyle = CheckBoxDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null content: @Composable (RowScope.() -> Unit)? = null
@@ -133,7 +127,7 @@ fun CheckBox(
.size(style.strokeSize) .size(style.strokeSize)
.scale(animatedStrokeScale) .scale(animatedStrokeScale)
.background(animatedColor, style.shape) .background(animatedColor, style.shape)
.borderOrElse(style.border, style.shape), .borderOrElse(style.borderWidth, colors.borderColor, style.shape),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
@@ -146,7 +140,7 @@ fun CheckBox(
scaleY = animatedContentLayer scaleY = animatedContentLayer
), ),
imageVector = FlexiIcons.CheckMark, imageVector = FlexiIcons.CheckMark,
style = IconDefaults.style.copy(tint = colors.contentColor) style = IconDefaults.style(tint = colors.contentColor)
) )
} }
content?.also { content -> content?.also { content ->
@@ -162,44 +156,70 @@ fun CheckBox(
* Defaults of check box. * Defaults of check box.
*/ */
object CheckBoxDefaults { object CheckBoxDefaults {
val colors: CheckBoxColors
/**
* Creates a [CheckBoxColors] with the default values.
* @param contentColor the color of the check mark.
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
* @param borderColor the color of the border.
* @return [CheckBoxColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultCheckBoxColors() contentColor: Color = CheckBoxProperties.ContentColor,
val style: CheckBoxStyle inactiveColor: Color = CheckBoxProperties.InactiveColor.toColor(),
activeColor: Color = CheckBoxProperties.ActiveColor.toColor(),
borderColor: Color = CheckBoxProperties.BorderColor.toColor()
) = CheckBoxColors(
contentColor = contentColor,
inactiveColor = inactiveColor,
activeColor = activeColor,
borderColor = borderColor
)
/**
* Creates a [CheckBoxStyle] with the default values.
* @param contentSpacing the spacing between the check mark and the content.
* @param contentSize the size of the check mark.
* @param strokeSize the stroke size.
* @param pressedGain the gain when pressed.
* @param hoveredGain the gain when hovered.
* @param shape the shape.
* @param borderWidth the border width.
* @return [CheckBoxStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultCheckBoxStyle() contentSpacing: Dp = CheckBoxProperties.ContentSpacing.toDp(),
contentSize: Dp = CheckBoxProperties.ContentSize,
strokeSize: Dp = CheckBoxProperties.StrokeSize,
pressedGain: Float = CheckBoxProperties.PressedGain,
hoveredGain: Float = CheckBoxProperties.HoveredGain,
shape: Shape = CheckBoxProperties.Shape,
borderWidth: Dp = CheckBoxProperties.BorderWidth.toDp()
) = CheckBoxStyle(
contentSpacing = contentSpacing,
contentSize = contentSize,
strokeSize = strokeSize,
pressedGain = pressedGain,
hoveredGain = hoveredGain,
shape = shape,
borderWidth = borderWidth
)
} }
@Composable @Stable
@ReadOnlyComposable internal object CheckBoxProperties {
private fun defaultCheckBoxColors() = CheckBoxColors( val ContentColor = Color.White
contentColor = Color.White, val InactiveColor = ColorsDescriptor.ThemeTertiary
inactiveColor = LocalColors.current.themeTertiary, val ActiveColor = ColorsDescriptor.ThemePrimary
activeColor = LocalColors.current.themePrimary val BorderColor = ColorsDescriptor.TextPrimary
) val ContentSpacing = SizesDescriptor.SpacingSecondary
val ContentSize = 13.dp
@Composable val StrokeSize = 20.dp
@ReadOnlyComposable const val PressedGain = 0.9f
private fun defaultCheckBoxStyle() = CheckBoxStyle( const val HoveredGain = 1.1f
contentSpacing = LocalSizes.current.spacingSecondary, val Shape: Shape = RoundedCornerShape(4.dp)
contentSize = DefaultContentSize, val BorderWidth = SizesDescriptor.BorderSizeTertiary
strokeSize = DefaultStrokeSize, }
pressedGain = DefaultPressedGain,
hoveredGain = DefaultHoveredGain,
shape = DefaultCheckBoxShape,
border = defaultCheckBoxBorder()
)
@Composable
@ReadOnlyComposable
private fun defaultCheckBoxBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
private val DefaultContentSize = 13.dp
private val DefaultStrokeSize = 20.dp
private val DefaultCheckBoxShape = RoundedCornerShape(4.dp)
private const val DefaultPressedGain = 0.9f
private const val DefaultHoveredGain = 1.1f

View File

@@ -19,7 +19,7 @@
* *
* This file is created by fankes on 2023/11/9. * This file is created by fankes on 2023/11/9.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
@@ -31,7 +31,6 @@ import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
@@ -60,7 +59,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -77,7 +75,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.InputMode import androidx.compose.ui.input.InputMode
@@ -106,77 +103,72 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.betterandroid.compose.extension.ui.solidColor
import com.highcapable.betterandroid.compose.extension.ui.window.Popup import com.highcapable.betterandroid.compose.extension.ui.window.Popup
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.resources.FlexiIcons import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.Dropdown import com.highcapable.flexiui.resources.icon.Dropdown
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* Colors defines for dropdown list. * Colors defines for dropdown list.
* @param endIconInactiveTint the tint of the end icon when inactive. * @see DropdownListDefaults.colors
* @param endIconActiveTint the tint of the end icon when active.
* @param borderInactiveColor the color of the border when inactive.
* @param borderActiveColor the color of the border when active.
* @param backgroundColor the background color.
*/ */
@Immutable @Immutable
data class DropdownListColors( data class DropdownListColors(
val endIconInactiveTint: Color, val endIconInactiveTint: Color,
val endIconActiveTint: Color, val endIconActiveTint: Color,
val backgroundColor: Color,
val borderInactiveColor: Color, val borderInactiveColor: Color,
val borderActiveColor: Color, val borderActiveColor: Color
val backgroundColor: Color
) )
/** /**
* Colors defines for dropdown menu. * Colors defines for dropdown menu.
* @param contentColor the color of the content. * @see DropdownMenuDefaults.colors
* @param activeColor the color of the active item.
* @param borderColor the color of the border.
*/ */
@Immutable @Immutable
data class DropdownMenuColors( data class DropdownMenuColors(
val contentColor: Color, val contentColor: Color,
val activeColor: Color, val activeColor: Color,
val backgroundColor: Color,
val borderColor: Color val borderColor: Color
) )
/** /**
* Style defines for dropdown list. * Style defines for dropdown list.
* @param padding the padding of the content. * @see DropdownListDefaults.style
* @param shape the shape.
* @param endIconSize the size of the end icon.
* @param borderInactive the border stroke when inactive.
* @param borderActive the border stroke when active.
*/ */
@Immutable @Immutable
data class DropdownListStyle( data class DropdownListStyle(
val padding: ComponentPadding, val padding: ComponentPadding,
val shape: Shape, val shape: Shape,
val endIconSize: Dp, val endIconSize: Dp,
val borderInactive: BorderStroke, val borderInactiveWidth: Dp,
val borderActive: BorderStroke val borderActiveWidth: Dp
) )
/** /**
* Style defines for dropdown menu. * Style defines for dropdown menu.
* @param inTransitionDuration the duration of the in transition. * @see DropdownMenuDefaults.style
* @param outTransitionDuration the duration of the out transition.
* @param contentStyle the content style of area box.
* @param borderStyle the brder style of area box.
*/ */
@Immutable @Immutable
data class DropdownMenuStyle( data class DropdownMenuStyle(
val padding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val contentPadding: ComponentPadding,
val contentShape: Shape,
val shadowSize: Dp,
val inTransitionDuration: Int, val inTransitionDuration: Int,
val outTransitionDuration: Int, val outTransitionDuration: Int
val contentStyle: AreaBoxStyle,
val borderStyle: AreaBoxStyle
) )
/** /**
@@ -202,10 +194,10 @@ fun DropdownList(
expanded: Boolean, expanded: Boolean,
onExpandedChange: (Boolean) -> Unit, onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: DropdownListColors = DropdownListDefaults.colors, colors: DropdownListColors = DropdownListDefaults.colors(),
style: DropdownListStyle = DropdownListDefaults.style, style: DropdownListStyle = DropdownListDefaults.style(),
menuColors: DropdownMenuColors = DropdownMenuDefaults.colors, menuColors: DropdownMenuColors = DropdownMenuDefaults.colors(),
menuStyle: DropdownMenuStyle = DropdownMenuDefaults.style, menuStyle: DropdownMenuStyle = DropdownMenuDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
scrollState: ScrollState = rememberScrollState(), scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true), properties: PopupProperties = PopupProperties(focusable = true),
@@ -221,24 +213,21 @@ fun DropdownList(
else -> colors.endIconInactiveTint else -> colors.endIconInactiveTint
}) })
val animatedBorderColor by animateColorAsState(when { val animatedBorderColor by animateColorAsState(when {
focused || hovered -> style.borderActive.solidColor focused || hovered -> colors.borderActiveColor
else -> style.borderInactive.solidColor else -> colors.borderInactiveColor
}) })
val animatedDirection by animateFloatAsState(if (expanded) 180f else 0f) val animatedDirection by animateFloatAsState(if (expanded) 180f else 0f)
val animatedBorderWidth by animateDpAsState(when { val animatedBorderWidth by animateDpAsState(when {
focused -> style.borderActive.width focused -> style.borderActiveWidth
else -> style.borderInactive.width else -> style.borderInactiveWidth
}) })
val border = when {
focused || hovered -> style.borderInactive
else -> style.borderInactive
}.copy(animatedBorderWidth, SolidColor(animatedBorderColor))
DropdownMenuBox( DropdownMenuBox(
modifier = Modifier.dropdownList( modifier = Modifier.dropdownList(
enabled = enabled, enabled = enabled,
colors = colors, colors = colors,
style = style, style = style,
border = border, borderColor = animatedBorderColor,
borderWidth = animatedBorderWidth,
focusRequester = focusRequester, focusRequester = focusRequester,
interactionSource = interactionSource, interactionSource = interactionSource,
then = modifier.rippleClickable( then = modifier.rippleClickable(
@@ -265,7 +254,7 @@ fun DropdownList(
rotationZ = animatedDirection rotationZ = animatedDirection
}.size(style.endIconSize), }.size(style.endIconSize),
imageVector = FlexiIcons.Dropdown, imageVector = FlexiIcons.Dropdown,
style = IconDefaults.style.copy(tint = animatedEndIconTint) style = IconDefaults.style(tint = animatedEndIconTint)
) )
} }
DropdownMenu( DropdownMenu(
@@ -301,8 +290,8 @@ fun DropdownMenu(
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: DropdownMenuColors = DropdownMenuDefaults.colors, colors: DropdownMenuColors = DropdownMenuDefaults.colors(),
style: DropdownMenuStyle = DropdownMenuDefaults.style, style: DropdownMenuStyle = DropdownMenuDefaults.style(),
offset: DpOffset = DpOffset(0.dp, 0.dp), offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(), scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true), properties: PopupProperties = PopupProperties(focusable = true),
@@ -383,7 +372,8 @@ fun DropdownMenuBox(
* @param modifier the [Modifier] to be applied to this dropdown menu item. * @param modifier the [Modifier] to be applied to this dropdown menu item.
* @param contentColor the color of the content. * @param contentColor the color of the content.
* @param activeColor the color of the active item. * @param activeColor the color of the active item.
* @param contentStyle the style of the content. * @param contentPadding the padding of the content.
* @param contentShape the shape of the content.
* @param enabled whether the dropdown menu item is enabled, default is true. * @param enabled whether the dropdown menu item is enabled, default is true.
* @param actived whether the dropdown menu item is actived, default is false. * @param actived whether the dropdown menu item is actived, default is false.
* @param interactionSource the interaction source of the dropdown menu item. * @param interactionSource the interaction source of the dropdown menu item.
@@ -395,7 +385,8 @@ fun DropdownMenuItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentColor: Color = Color.Unspecified, contentColor: Color = Color.Unspecified,
activeColor: Color = Color.Unspecified, activeColor: Color = Color.Unspecified,
contentStyle: AreaBoxStyle? = null, contentPadding: ComponentPadding? = null,
contentShape: Shape? = null,
enabled: Boolean = true, enabled: Boolean = true,
actived: Boolean = false, actived: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@@ -403,13 +394,16 @@ fun DropdownMenuItem(
) { ) {
val currentColor = contentColor.orNull() val currentColor = contentColor.orNull()
?: LocalDropdownMenuContentColor.current.orNull() ?: LocalDropdownMenuContentColor.current.orNull()
?: DropdownMenuDefaults.colors.contentColor ?: DropdownMenuDefaults.colors().contentColor
val currentActiveColor = activeColor.orNull() val currentActiveColor = activeColor.orNull()
?: LocalDropdownMenuActiveColor.current.orNull() ?: LocalDropdownMenuActiveColor.current.orNull()
?: DropdownMenuDefaults.colors.activeColor ?: DropdownMenuDefaults.colors().activeColor
val currentStyle = contentStyle val currentPadding = contentPadding
?: LocalDropdownMenuContentStyle.current ?: LocalDropdownMenuContentPadding.current
?: DropdownMenuDefaults.style.contentStyle ?: DropdownMenuDefaults.style().contentPadding
val currentShape = contentShape
?: LocalDropdownMenuContentShape.current
?: DropdownMenuDefaults.style().contentShape
AreaRow( AreaRow(
modifier = Modifier.componentState(enabled) modifier = Modifier.componentState(enabled)
.then(modifier) .then(modifier)
@@ -425,8 +419,8 @@ fun DropdownMenuItem(
interactionSource = interactionSource, interactionSource = interactionSource,
onClick = onClick onClick = onClick
), ),
color = if (actived) currentActiveColor else Color.Transparent, colors = AreaBoxDefaults.colors(backgroundColor = if (actived) currentActiveColor else Color.Transparent),
style = currentStyle, style = AreaBoxDefaults.style(padding = currentPadding, shape = currentShape),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
@@ -489,13 +483,22 @@ private fun DropdownMenuContent(
transformOrigin = transformOriginState.value transformOrigin = transformOriginState.value
} }
}, },
color = colors.borderColor, colors = AreaBoxDefaults.colors(
style = style.borderStyle backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
LocalDropdownMenuContentColor provides colors.contentColor, LocalDropdownMenuContentColor provides colors.contentColor,
LocalDropdownMenuActiveColor provides colors.activeColor, LocalDropdownMenuActiveColor provides colors.activeColor,
LocalDropdownMenuContentStyle provides style.contentStyle LocalDropdownMenuContentPadding provides style.contentPadding,
LocalDropdownMenuContentShape provides style.contentShape
) { content() } ) { content() }
} }
} }
@@ -504,7 +507,8 @@ private fun Modifier.dropdownList(
enabled: Boolean, enabled: Boolean,
colors: DropdownListColors, colors: DropdownListColors,
style: DropdownListStyle, style: DropdownListStyle,
border: BorderStroke, borderColor: Color,
borderWidth: Dp,
focusRequester: FocusRequester, focusRequester: FocusRequester,
interactionSource: MutableInteractionSource, interactionSource: MutableInteractionSource,
then: Modifier then: Modifier
@@ -514,7 +518,8 @@ private fun Modifier.dropdownList(
properties["enabled"] = enabled properties["enabled"] = enabled
properties["colors"] = colors properties["colors"] = colors
properties["style"] = style properties["style"] = style
properties["border"] = border properties["borderColor"] = borderColor
properties["borderWidth"] = borderWidth
} }
) { ) {
componentState(enabled) componentState(enabled)
@@ -523,7 +528,7 @@ private fun Modifier.dropdownList(
.hoverable(interactionSource, enabled) .hoverable(interactionSource, enabled)
.clip(style.shape) .clip(style.shape)
.background(colors.backgroundColor, style.shape) .background(colors.backgroundColor, style.shape)
.borderOrElse(border, style.shape) .borderOrElse(borderWidth, borderColor, style.shape)
.then(then) .then(then)
.padding(style.padding) .padding(style.padding)
} }
@@ -611,92 +616,150 @@ private data class DropdownMenuPositionProvider(
* Defaults of dropdown list. * Defaults of dropdown list.
*/ */
object DropdownListDefaults { object DropdownListDefaults {
val colors: DropdownListColors
/**
* Creates a [DropdownListColors] with the default values.
* @param endIconInactiveTint the tint of the end icon when inactive.
* @param endIconActiveTint the tint of the end icon when active.
* @param backgroundColor the background color.
* @param borderInactiveColor the color of the border when inactive.
* @param borderActiveColor the color of the border when active.
* @return [DropdownListColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultDropdownListColors() endIconInactiveTint: Color = DropdownListProperties.EndIconInactiveTint.toColor(),
val style: DropdownListStyle endIconActiveTint: Color = DropdownListProperties.EndIconActiveTint.toColor(),
backgroundColor: Color = DropdownListProperties.BackgroundColor,
borderInactiveColor: Color = DropdownListProperties.BorderInactiveColor.toColor(),
borderActiveColor: Color = DropdownListProperties.BorderActiveColor.toColor()
) = DropdownListColors(
endIconInactiveTint = endIconInactiveTint,
endIconActiveTint = endIconActiveTint,
backgroundColor = backgroundColor,
borderInactiveColor = borderInactiveColor,
borderActiveColor = borderActiveColor
)
/**
* Creates a [DropdownListStyle] with the default values.
* @param padding the padding of the content.
* @param shape the shape.
* @param endIconSize the size of the end icon.
* @param borderInactiveWidth the width of the border when inactive.
* @param borderActiveWidth the width of the border when active.
* @return [DropdownListStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultDropdownListStyle() padding: ComponentPadding = DropdownListProperties.Padding.toPadding(),
shape: Shape = AreaBoxDefaults.childShape(),
endIconSize: Dp = DropdownListProperties.EndIconSize.toDp(),
borderInactiveWidth: Dp = DropdownListProperties.BorderInactiveWidth.toDp(),
borderActiveWidth: Dp = DropdownListProperties.BorderActiveWidth.toDp()
) = DropdownListStyle(
padding = padding,
shape = shape,
endIconSize = endIconSize,
borderInactiveWidth = borderInactiveWidth,
borderActiveWidth = borderActiveWidth
)
} }
/** /**
* Defaults of dropdown menu. * Defaults of dropdown menu.
*/ */
object DropdownMenuDefaults { object DropdownMenuDefaults {
val colors: DropdownMenuColors
/**
* Creates a [DropdownMenuColors] with the default values.
* @param contentColor the color of the content.
* @param activeColor the color of the active item.
* @param backgroundColor the background color.
* @param borderColor the color of the border.
* @return [DropdownMenuColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultDropdownMenuColors() contentColor: Color = DropdownMenuProperties.ContentColor.toColor(),
val style: DropdownMenuStyle activeColor: Color = DropdownMenuProperties.ActiveColor.toColor().copy(alpha = 0.3f),
backgroundColor: Color = DropdownMenuProperties.BackgroundColor.toColor(),
borderColor: Color = DropdownMenuProperties.BorderColor.toColor()
) = DropdownMenuColors(
contentColor = contentColor,
activeColor = activeColor,
backgroundColor = backgroundColor,
borderColor = borderColor
)
/**
* Creates a [DropdownMenuStyle] with the default values.
* @param padding the menu padding.
* @param shape the menu shape.
* @param borderWidth the menu border width.
* @param contentPadding the content padding.
* @param contentShape the content shape.
* @param shadowSize the shadow size.
* @param inTransitionDuration the duration of the in transition.
* @param outTransitionDuration the duration of the out transition.
* @return [DropdownMenuStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultDropdownMenuStyle() padding: ComponentPadding = DropdownMenuProperties.Padding.toPadding(),
shape: Shape = DropdownMenuProperties.Shape.toShape(),
borderWidth: Dp = DropdownMenuProperties.BorderWidth.toDp(),
contentPadding: ComponentPadding = DropdownMenuProperties.ContentPadding,
contentShape: Shape = DropdownMenuProperties.ContentShape.toShape(),
shadowSize: Dp = DropdownMenuProperties.ShadowSize.toDp(),
inTransitionDuration: Int = DropdownMenuProperties.InTransitionDuration,
outTransitionDuration: Int = DropdownMenuProperties.OutTransitionDuration
) = DropdownMenuStyle(
padding = padding,
shape = shape,
borderWidth = borderWidth,
contentPadding = contentPadding,
contentShape = contentShape,
shadowSize = shadowSize,
inTransitionDuration = inTransitionDuration,
outTransitionDuration = outTransitionDuration
)
} }
private val LocalDropdownMenuContentColor = compositionLocalOf { Color.Unspecified } @Stable
internal object DropdownListProperties {
val EndIconInactiveTint = ColorsDescriptor.ThemeSecondary
val EndIconActiveTint = ColorsDescriptor.ThemePrimary
val BackgroundColor = Color.Transparent
val BorderInactiveColor = ColorsDescriptor.ThemeSecondary
val BorderActiveColor = ColorsDescriptor.ThemePrimary
val Padding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
val EndIconSize = SizesDescriptor.IconSizeTertiary
val BorderInactiveWidth = SizesDescriptor.BorderSizeSecondary
val BorderActiveWidth = SizesDescriptor.BorderSizePrimary
}
@Stable
internal object DropdownMenuProperties {
val ContentColor = ColorsDescriptor.TextPrimary
val ActiveColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = AreaBoxProperties.BackgroundColor
val BorderColor = AreaBoxProperties.BorderColor
val Padding = PaddingDescriptor(SizesDescriptor.SpacingTertiary)
val Shape = ShapesDescriptor.Primary
val BorderWidth = AreaBoxProperties.BorderWidth
val ContentPadding = ComponentPadding(horizontal = 16.dp)
val ContentShape = ShapesDescriptor.Secondary
val ShadowSize = SizesDescriptor.ZoomSizeTertiary
const val InTransitionDuration = 120
const val OutTransitionDuration = 90
}
private val LocalDropdownMenuActiveColor = compositionLocalOf { Color.Unspecified } private val LocalDropdownMenuActiveColor = compositionLocalOf { Color.Unspecified }
private val LocalDropdownMenuContentStyle = compositionLocalOf<AreaBoxStyle?> { null } private val LocalDropdownMenuContentColor = compositionLocalOf { Color.Unspecified }
private val LocalDropdownMenuContentPadding = compositionLocalOf<ComponentPadding?> { null }
@Composable private val LocalDropdownMenuContentShape = compositionLocalOf<Shape?> { null }
@ReadOnlyComposable
private fun defaultDropdownListColors() = DropdownListColors(
endIconInactiveTint = LocalColors.current.themeSecondary,
endIconActiveTint = LocalColors.current.themePrimary,
borderInactiveColor = LocalColors.current.themeSecondary,
borderActiveColor = LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultDropdownMenuColors() = DropdownMenuColors(
contentColor = LocalColors.current.textPrimary,
activeColor = LocalColors.current.themePrimary.copy(alpha = 0.3f),
borderColor = LocalColors.current.backgroundSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultDropdownListStyle() = DropdownListStyle(
padding = ComponentPadding(LocalSizes.current.spacingSecondary),
shape = withAreaBoxShape(),
endIconSize = LocalSizes.current.iconSizeTertiary,
borderInactive = defaultDropdownListInactiveBorder(),
borderActive = defaultDropdownListActiveBorder()
)
@Composable
@ReadOnlyComposable
private fun defaultDropdownMenuStyle() = DropdownMenuStyle(
inTransitionDuration = DefaultInTransitionDuration,
outTransitionDuration = DefaultOutTransitionDuration,
contentStyle = AreaBoxDefaults.style.copy(
padding = ComponentPadding(horizontal = DefaultMenuContentPadding),
shape = LocalShapes.current.secondary
),
borderStyle = AreaBoxDefaults.style.copy(
padding = ComponentPadding(LocalSizes.current.spacingTertiary),
shadowSize = LocalSizes.current.zoomSizeTertiary,
shape = LocalShapes.current.primary
)
)
@Composable
@ReadOnlyComposable
private fun defaultDropdownListInactiveBorder() = BorderStroke(LocalSizes.current.borderSizeSecondary, LocalColors.current.themeSecondary)
@Composable
@ReadOnlyComposable
private fun defaultDropdownListActiveBorder() = BorderStroke(LocalSizes.current.borderSizePrimary, LocalColors.current.themePrimary)
private val DefaultMenuContentPadding = 16.dp
private const val DefaultInTransitionDuration = 120
private const val DefaultOutTransitionDuration = 90
private val MenuItemMinWidth = 112.dp private val MenuItemMinWidth = 112.dp
private val MenuItemMaxWidth = 280.dp private val MenuItemMaxWidth = 280.dp

View File

@@ -27,7 +27,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
@@ -48,13 +48,14 @@ import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.isSpecified import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.isUnspecified
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toDp
/** /**
* Style defines for icon. * Style defines for basic icon.
* @param size the size. * @see IconDefaults.style
* @param tint the tint.
*/ */
@Immutable @Immutable
data class IconStyle( data class IconStyle(
@@ -67,14 +68,14 @@ data class IconStyle(
* @param imageVector the vector image to be drawn. * @param imageVector the vector image to be drawn.
* @param contentDescription the content description for this icon. * @param contentDescription the content description for this icon.
* @param modifier the [Modifier] to be applied to this icon. * @param modifier the [Modifier] to be applied to this icon.
* @param style the style of this icon, default is [IconDefaults.style]. * @param style the style of this icon.
*/ */
@Composable @Composable
fun Icon( fun Icon(
imageVector: ImageVector, imageVector: ImageVector,
contentDescription: String? = null, contentDescription: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
style: IconStyle = IconDefaults.style style: IconStyle? = null
) { ) {
val painter = rememberVectorPainter(imageVector) val painter = rememberVectorPainter(imageVector)
Icon(painter, contentDescription, modifier, style) Icon(painter, contentDescription, modifier, style)
@@ -85,16 +86,23 @@ fun Icon(
* @param painter the painter to be drawn. * @param painter the painter to be drawn.
* @param contentDescription the content description for this icon. * @param contentDescription the content description for this icon.
* @param modifier the [Modifier] to be applied to this icon. * @param modifier the [Modifier] to be applied to this icon.
* @param style the style of this icon, default is [IconDefaults.style]. * @param style the style of this icon.
*/ */
@Composable @Composable
fun Icon( fun Icon(
painter: Painter, painter: Painter,
contentDescription: String? = null, contentDescription: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
style: IconStyle = IconDefaults.style style: IconStyle? = null
) { ) {
val colorFilter = if (style.tint.isUnspecified) null else ColorFilter.tint(style.tint) val defaultStyle = LocalIconStyle.current.orNull() ?: IconDefaults.style()
val currentStyle = style?.let {
it.copy(
size = it.size.orNull() ?: defaultStyle.size,
tint = it.tint.orNull() ?: defaultStyle.tint
)
} ?: defaultStyle
val colorFilter = if (currentStyle.tint.isUnspecified) null else ColorFilter.tint(currentStyle.tint)
val semantics = if (contentDescription != null) val semantics = if (contentDescription != null)
Modifier.semantics { Modifier.semantics {
this.contentDescription = contentDescription this.contentDescription = contentDescription
@@ -103,7 +111,7 @@ fun Icon(
else Modifier else Modifier
Box( Box(
modifier = modifier.toolingGraphicsLayer() modifier = modifier.toolingGraphicsLayer()
.defaultSizeFor(style, painter) .defaultSizeFor(currentStyle, painter)
.paint( .paint(
painter, painter,
colorFilter = colorFilter, colorFilter = colorFilter,
@@ -127,30 +135,49 @@ private fun Modifier.defaultSizeFor(
style.size.isSpecified || style.size.isSpecified ||
painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize == Size.Unspecified ||
painter.intrinsicSize.isInfinite() -> painter.intrinsicSize.isInfinite() ->
Modifier.size(style.size.orNull() ?: defaultIconSize()) Modifier.size(style.size.orNull() ?: IconDefaults.style().size)
else -> Modifier else -> Modifier
}) })
} }
/** /**
* Defaults of icon. * Defaults of basic icon.
*/ */
object IconDefaults { object IconDefaults {
val style: IconStyle
/**
* Creates a [IconStyle] with the default values.
* @param size the size.
* @param tint the tint.
* @return [IconStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = LocalIconStyle.current size: Dp = IconProperties.Size.toDp(),
tint: Color = IconProperties.Tint
) = IconStyle(
size = size,
tint = tint
)
} }
internal val LocalIconStyle = compositionLocalOf { DefaultIconStyle } @Stable
internal object IconProperties {
val Size = SizesDescriptor.IconSizePrimary
val Tint = Color.Unspecified
}
/**
* Composition local containing the preferred [IconStyle]
* that will be used by [Icon] by default.
*/
val LocalIconStyle = compositionLocalOf { DefaultIconStyle }
private val DefaultIconStyle = IconStyle( private val DefaultIconStyle = IconStyle(
size = Dp.Unspecified, size = Dp.Unspecified,
tint = Color.Unspecified tint = Color.Unspecified
) )
@Composable private fun IconStyle.orNull() = if (size.isUnspecified && tint.isUnspecified) null else this
@ReadOnlyComposable
private fun defaultIconSize() = LocalSizes.current.iconSizePrimary
private fun Size.isInfinite() = width.isInfinite() && height.isInfinite() private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()

View File

@@ -32,50 +32,52 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.LocalTypography import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.resources.FlexiIcons import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.ArrowForward import com.highcapable.flexiui.resources.icon.ArrowForward
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import com.highcapable.flexiui.toTextStyle
/** /**
* Colors defines for item box. * Colors defines for item box.
* @param backgroundColor the background color. * @see ItemBoxDefaults.colors
* @param titleTextColor the title text color.
* @param subTextColor the sub text color.
* @param arrowIconTint the arrow icon tint.
*/ */
@Immutable @Immutable
data class ItemBoxColors( data class ItemBoxColors(
val backgroundColor: Color, val backgroundColor: Color,
val titleTextColor: Color, val titleTextColor: Color,
val subTextColor: Color, val subTextColor: Color,
val arrowIconTint: Color val arrowIconTint: Color,
val borderColor: Color
) )
/** /**
* Style defines for item box. * Style defines for item box.
* @param boxStyle the style of area box. * @see ItemBoxDefaults.style
* @param contentSpacing the spacing between the components of content.
* @param titleTextStyle the title text style.
* @param subTextStyle the sub text style.
*/ */
@Immutable @Immutable
data class ItemBoxStyle( data class ItemBoxStyle(
val boxStyle: AreaBoxStyle,
val contentSpacing: Dp, val contentSpacing: Dp,
val titleTextStyle: TextStyle, val titleTextStyle: TextStyle,
val subTextStyle: TextStyle val subTextStyle: TextStyle,
val shape: Shape,
val borderWidth: Dp,
val shadowSize: Dp
) )
/** /**
@@ -96,8 +98,8 @@ data class ItemBoxStyle(
fun HorizontalItemBox( fun HorizontalItemBox(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ItemBoxColors = ItemBoxDefaults.colors, colors: ItemBoxColors = ItemBoxDefaults.colors(),
style: ItemBoxStyle = ItemBoxDefaults.style, style: ItemBoxStyle = ItemBoxDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
showArrowIcon: Boolean = true, showArrowIcon: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@@ -112,8 +114,15 @@ fun HorizontalItemBox(
onClick = onClick onClick = onClick
), ),
initializer = { componentState(enabled) }, initializer = { componentState(enabled) },
color = colors.backgroundColor, colors = AreaBoxDefaults.colors(
style = style.boxStyle backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -134,7 +143,7 @@ fun HorizontalItemBox(
} }
if (showArrowIcon) Icon( if (showArrowIcon) Icon(
imageVector = FlexiIcons.ArrowForward, imageVector = FlexiIcons.ArrowForward,
style = IconDefaults.style.copy( style = IconDefaults.style(
size = DefaultArrowIconSize, size = DefaultArrowIconSize,
tint = colors.arrowIconTint tint = colors.arrowIconTint
) )
@@ -160,8 +169,8 @@ fun HorizontalItemBox(
fun VerticalItemBox( fun VerticalItemBox(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ItemBoxColors = ItemBoxDefaults.colors, colors: ItemBoxColors = ItemBoxDefaults.colors(),
style: ItemBoxStyle = ItemBoxDefaults.style, style: ItemBoxStyle = ItemBoxDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
logoImage: @Composable (() -> Unit)? = null, logoImage: @Composable (() -> Unit)? = null,
@@ -175,8 +184,15 @@ fun VerticalItemBox(
onClick = onClick onClick = onClick
), ),
initializer = { componentState(enabled) }, initializer = { componentState(enabled) },
color = colors.backgroundColor, colors = AreaBoxDefaults.colors(
style = style.boxStyle, backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(style.contentSpacing / VerticalContentSpacingRatio) verticalArrangement = Arrangement.spacedBy(style.contentSpacing / VerticalContentSpacingRatio)
) { ) {
@@ -213,31 +229,73 @@ private fun ItemBoxContent(
* Defaults of item box. * Defaults of item box.
*/ */
object ItemBoxDefaults { object ItemBoxDefaults {
val colors: ItemBoxColors
/**
* Creates a [ItemBoxColors] with the default values.
* @param backgroundColor the background color.
* @param titleTextColor the title text color.
* @param subTextColor the sub text color.
* @param arrowIconTint the arrow icon tint.
* @param borderColor the border color.
* @return [ItemBoxColors]
*/
@Composable @Composable
get() = defaultItemBoxColors() fun colors(
val style: ItemBoxStyle backgroundColor: Color = ItemBoxProperties.BackgroundColor.toColor(),
titleTextColor: Color = ItemBoxProperties.TitleTextColor.toColor(),
subTextColor: Color = ItemBoxProperties.SubTextColor.toColor(),
arrowIconTint: Color = ItemBoxProperties.ArrowIconTint.toColor(),
borderColor: Color = ItemBoxProperties.BorderColor.toColor()
) = ItemBoxColors(
backgroundColor = backgroundColor,
titleTextColor = titleTextColor,
subTextColor = subTextColor,
arrowIconTint = arrowIconTint,
borderColor = borderColor
)
/**
* Creates a [ItemBoxStyle] with the default values.
* @param contentSpacing the spacing between the components of content.
* @param titleTextStyle the title text style.
* @param subTextStyle the sub text style.
* @param shape the shape.
* @param borderWidth the border width.
* @param shadowSize the shadow size.
* @return [ItemBoxStyle]
*/
@Composable @Composable
get() = defaultItemBoxStyle() fun style(
contentSpacing: Dp = ItemBoxProperties.ContentSpacing.toDp(),
titleTextStyle: TextStyle = ItemBoxProperties.TitleTextStyle.toTextStyle(),
subTextStyle: TextStyle = ItemBoxProperties.SubTextStyle.toTextStyle(),
shape: Shape = ItemBoxProperties.Shape.toShape(),
borderWidth: Dp = ItemBoxProperties.BorderWidth.toDp(),
shadowSize: Dp = ItemBoxProperties.ShadowSize
) = ItemBoxStyle(
contentSpacing = contentSpacing,
titleTextStyle = titleTextStyle,
subTextStyle = subTextStyle,
shape = shape,
borderWidth = borderWidth,
shadowSize = shadowSize
)
} }
@Composable @Stable
@ReadOnlyComposable internal object ItemBoxProperties {
private fun defaultItemBoxColors() = ItemBoxColors( val BackgroundColor = AreaBoxProperties.BackgroundColor
backgroundColor = AreaBoxDefaults.color, val TitleTextColor = ColorsDescriptor.TextPrimary
titleTextColor = LocalColors.current.textPrimary, val SubTextColor = ColorsDescriptor.TextSecondary
subTextColor = LocalColors.current.textSecondary, val ArrowIconTint = ColorsDescriptor.TextSecondary
arrowIconTint = LocalColors.current.textSecondary val BorderColor = AreaBoxProperties.BorderColor
) val ContentSpacing = SizesDescriptor.SpacingSecondary
val TitleTextStyle = TypographyDescriptor.Primary
@Composable val SubTextStyle = TypographyDescriptor.Secondary
@ReadOnlyComposable val Shape = AreaBoxProperties.Shape
private fun defaultItemBoxStyle() = ItemBoxStyle( val BorderWidth = AreaBoxProperties.BorderWidth
boxStyle = AreaBoxDefaults.style, val ShadowSize = AreaBoxProperties.ShadowSize
contentSpacing = LocalSizes.current.spacingSecondary, }
titleTextStyle = LocalTypography.current.primary,
subTextStyle = LocalTypography.current.secondary
)
private val DefaultArrowIconSize = 15.dp private val DefaultArrowIconSize = 15.dp

View File

@@ -44,7 +44,7 @@ import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -58,21 +58,23 @@ import androidx.compose.ui.unit.Dp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.InteractionDefaults import com.highcapable.flexiui.component.interaction.InteractionDefaults
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
/** /**
* Colors defines for navigation bar. * Colors defines for navigation bar.
* @param backgroundColor the background color. * @see NavigationBarDefaults.colors
* @param indicatorColor the indicator color.
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
*/ */
@Immutable @Immutable
data class NavigationBarColors( data class NavigationBarColors(
val backgroundColor: Color, val backgroundColor: Color,
val borderColor: Color,
val indicatorColor: Color, val indicatorColor: Color,
val selectedContentColor: Color, val selectedContentColor: Color,
val unselectedContentColor: Color val unselectedContentColor: Color
@@ -80,14 +82,14 @@ data class NavigationBarColors(
/** /**
* Style defines for navigation bar. * Style defines for navigation bar.
* @param boxStyle the style of area box. * @see NavigationBarDefaults.style
* @param contentSpacing the spacing between the components of content.
* @param contentPadding the padding of content.
* @param contentShape the content shape.
*/ */
@Immutable @Immutable
data class NavigationBarStyle( data class NavigationBarStyle(
val boxStyle: AreaBoxStyle, val padding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val shadowSize: Dp,
val contentSpacing: Dp, val contentSpacing: Dp,
val contentPadding: ComponentPadding, val contentPadding: ComponentPadding,
val contentShape: Shape val contentShape: Shape
@@ -106,16 +108,24 @@ data class NavigationBarStyle(
@Composable @Composable
fun NavigationBarRow( fun NavigationBarRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: NavigationBarColors = NavigationBarDefaults.colors, colors: NavigationBarColors = NavigationBarDefaults.colors(),
style: NavigationBarStyle = NavigationBarDefaults.style, style: NavigationBarStyle = NavigationBarDefaults.style(),
arrangement: Arrangement.Horizontal = Arrangement.SpaceBetween, arrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
content: @Composable RowScope.() -> Unit content: @Composable RowScope.() -> Unit
) { ) {
NavigationBarStyleBox(modifier, horizontal = true, colors, style) { NavigationBarStyleBox(modifier, horizontal = true, colors, style) {
AreaRow( AreaRow(
modifier = Modifier.fillMaxWidth().selectableGroup(), modifier = Modifier.fillMaxWidth().selectableGroup(),
color = colors.backgroundColor, colors = AreaBoxDefaults.colors(
style = style.boxStyle, backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalArrangement = arrangement, horizontalArrangement = arrangement,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
content = content content = content
@@ -136,16 +146,24 @@ fun NavigationBarRow(
@Composable @Composable
fun NavigationBarColumn( fun NavigationBarColumn(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: NavigationBarColors = NavigationBarDefaults.colors, colors: NavigationBarColors = NavigationBarDefaults.colors(),
style: NavigationBarStyle = NavigationBarDefaults.style, style: NavigationBarStyle = NavigationBarDefaults.style(),
arrangement: Arrangement.Vertical = Arrangement.SpaceBetween, arrangement: Arrangement.Vertical = Arrangement.SpaceBetween,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
NavigationBarStyleBox(modifier, horizontal = false, colors, style) { NavigationBarStyleBox(modifier, horizontal = false, colors, style) {
AreaColumn( AreaColumn(
modifier = Modifier.fillMaxWidth().selectableGroup(), modifier = Modifier.fillMaxWidth().selectableGroup(),
color = colors.backgroundColor, colors = AreaBoxDefaults.colors(
style = style.boxStyle, backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = arrangement, verticalArrangement = arrangement,
content = content content = content
@@ -186,16 +204,16 @@ fun NavigationBarItem(
text: @Composable (() -> Unit)? = null text: @Composable (() -> Unit)? = null
) { ) {
val currentHorizontal = horizontal ?: LocalHorizontalNavigationBar.current val currentHorizontal = horizontal ?: LocalHorizontalNavigationBar.current
val currentColors = colors ?: LocalNavigationBarColors.current ?: NavigationBarDefaults.colors val currentColors = colors ?: LocalNavigationBarColors.current ?: NavigationBarDefaults.colors()
val currentContentSpacing = contentSpacing.orNull() val currentContentSpacing = contentSpacing.orNull()
?: LocalNavigationBarContentSpacing.current.orNull() ?: LocalNavigationBarContentSpacing.current.orNull()
?: NavigationBarDefaults.style.contentSpacing ?: NavigationBarDefaults.style().contentSpacing
val currentContentPadding = contentPadding val currentContentPadding = contentPadding
?: LocalNavigationBarContentPadding.current ?: LocalNavigationBarContentPadding.current
?: NavigationBarDefaults.style.contentPadding ?: NavigationBarDefaults.style().contentPadding
val currentContentShape = contentShape val currentContentShape = contentShape
?: LocalNavigationBarContentShape.current ?: LocalNavigationBarContentShape.current
?: NavigationBarDefaults.style.contentShape ?: NavigationBarDefaults.style().contentShape
val animatedIndicatorColor by animateColorAsState(if (selected) currentColors.indicatorColor else Color.Transparent) val animatedIndicatorColor by animateColorAsState(if (selected) currentColors.indicatorColor else Color.Transparent)
val animatedContentColor by animateColorAsState(if (selected) currentColors.selectedContentColor else currentColors.unselectedContentColor) val animatedContentColor by animateColorAsState(if (selected) currentColors.selectedContentColor else currentColors.unselectedContentColor)
val currentIconStyle = LocalIconStyle.current.copy(tint = animatedContentColor) val currentIconStyle = LocalIconStyle.current.copy(tint = animatedContentColor)
@@ -206,7 +224,7 @@ fun NavigationBarItem(
.then(modifier) .then(modifier)
.background(animatedIndicatorColor) .background(animatedIndicatorColor)
.rippleClickable( .rippleClickable(
rippleStyle = InteractionDefaults.rippleStyle.copy(color = currentColors.indicatorColor), rippleStyle = InteractionDefaults.rippleStyle(color = currentColors.indicatorColor),
enabled = enabled, enabled = enabled,
role = Role.Tab, role = Role.Tab,
interactionSource = interactionSource, interactionSource = interactionSource,
@@ -275,45 +293,85 @@ private fun NavigationBarStyleBox(
* Defaults of navigation bar. * Defaults of navigation bar.
*/ */
object NavigationBarDefaults { object NavigationBarDefaults {
val colors: NavigationBarColors
/**
* Creates a [NavigationBarColors] with the default values.
* @param backgroundColor the background color.
* @param borderColor the border color.
* @param indicatorColor the indicator color.
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
* @return [NavigationBarColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultNavigationBarColors() backgroundColor: Color = NavigationBarProperties.BackgroundColor.toColor(),
val style: NavigationBarStyle borderColor: Color = NavigationBarProperties.BorderColor.toColor(),
indicatorColor: Color = NavigationBarProperties.IndicatorColor.toColor(),
selectedContentColor: Color = NavigationBarProperties.SelectedContentColor.toColor(),
unselectedContentColor: Color = NavigationBarProperties.UnselectedContentColor.toColor()
) = NavigationBarColors(
backgroundColor = backgroundColor,
borderColor = borderColor,
indicatorColor = indicatorColor,
selectedContentColor = selectedContentColor,
unselectedContentColor = unselectedContentColor
)
/**
* Creates a [NavigationBarStyle] with the default values.
* @param padding the padding.
* @param shape the shape.
* @param borderWidth the border width.
* @param shadowSize the shadow size.
* @param contentSpacing the spacing between the components of content.
* @param contentPadding the padding of content.
* @param contentShape the content shape.
* @return [NavigationBarStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultNavigationBarStyle() padding: ComponentPadding = NavigationBarProperties.Padding.toPadding(),
shape: Shape = NavigationBarProperties.Shape.toShape(),
borderWidth: Dp = NavigationBarProperties.BorderWidth.toDp(),
shadowSize: Dp = NavigationBarProperties.ShadowSize,
contentSpacing: Dp = NavigationBarProperties.ContentSpacing.toDp(),
contentPadding: ComponentPadding = NavigationBarProperties.ContentPadding.toPadding(),
contentShape: Shape = NavigationBarProperties.Shape.toShape()
) = NavigationBarStyle(
padding = padding,
shape = shape,
borderWidth = borderWidth,
shadowSize = shadowSize,
contentSpacing = contentSpacing,
contentPadding = contentPadding,
contentShape = contentShape
)
}
@Stable
internal object NavigationBarProperties {
val BackgroundColor = AreaBoxProperties.BackgroundColor
val BorderColor = AreaBoxProperties.BorderColor
val IndicatorColor = ColorsDescriptor.ThemeTertiary
val SelectedContentColor = ColorsDescriptor.ThemePrimary
val UnselectedContentColor = ColorsDescriptor.TextSecondary
val Padding = AreaBoxProperties.Padding
val Shape = AreaBoxProperties.Shape
val BorderWidth = AreaBoxProperties.BorderWidth
val ShadowSize = AreaBoxProperties.ShadowSize
val ContentSpacing = SizesDescriptor.SpacingPrimary
val ContentPadding = PaddingDescriptor(
horizontal = SizesDescriptor.SpacingPrimary,
vertical = SizesDescriptor.SpacingSecondary
)
} }
private val LocalHorizontalNavigationBar = compositionLocalOf { true } private val LocalHorizontalNavigationBar = compositionLocalOf { true }
private val LocalNavigationBarColors = compositionLocalOf<NavigationBarColors?> { null } private val LocalNavigationBarColors = compositionLocalOf<NavigationBarColors?> { null }
private val LocalNavigationBarContentSpacing = compositionLocalOf { Dp.Unspecified } private val LocalNavigationBarContentSpacing = compositionLocalOf { Dp.Unspecified }
private val LocalNavigationBarContentPadding = compositionLocalOf<ComponentPadding?> { null }
private val LocalNavigationBarContentPadding = compositionLocalOf<PaddingValues?> { null }
private val LocalNavigationBarContentShape = compositionLocalOf<Shape?> { null } private val LocalNavigationBarContentShape = compositionLocalOf<Shape?> { null }
@Composable
@ReadOnlyComposable
private fun defaultNavigationBarColors() = NavigationBarColors(
backgroundColor = AreaBoxDefaults.color,
indicatorColor = LocalColors.current.themeTertiary,
selectedContentColor = LocalColors.current.themePrimary,
unselectedContentColor = LocalColors.current.textSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultNavigationBarStyle() = NavigationBarStyle(
boxStyle = AreaBoxDefaults.style,
contentSpacing = LocalSizes.current.spacingSecondary,
contentPadding = ComponentPadding(
horizontal = LocalSizes.current.spacingPrimary,
vertical = LocalSizes.current.spacingSecondary
),
contentShape = withAreaBoxShape()
)
private const val VerticalContentSpacingRatio = 1.6f private const val VerticalContentSpacingRatio = 1.6f

View File

@@ -19,7 +19,7 @@
* *
* This file is created by fankes on 2023/11/8. * This file is created by fankes on 2023/11/8.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ObjectPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
@@ -37,7 +37,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -52,14 +51,16 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.toColor
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
/** /**
* Style interface for progress indicator. * Style interface for progress indicator.
* @see CircularIndicatorStyle
* @see LinearIndicatorStyle
*/ */
@Stable @Stable
interface ProgressIndicatorStyle { interface ProgressIndicatorStyle {
@@ -69,40 +70,45 @@ interface ProgressIndicatorStyle {
/** /**
* Animation interface for progress indicator. * Animation interface for progress indicator.
* @see CircularIndicatorAnimation
* @see LinearIndicatorAnimation
*/ */
@Stable @Stable
interface ProgressIndicatorAnimation { interface ProgressIndicatorAnimation {
val duration: Int val duration: Int
} }
/**
* Colors defines for progress indicator.
* @see CircularIndicatorDefaults.colors
* @see LinearIndicatorDefaults.colors
*/
@Immutable
data class ProgressIndicatorColors(
val foregroundColor: Color,
val backgroundColor: Color
)
/** /**
* Style defines for circular progress indicator. * Style defines for circular progress indicator.
* @param strokeWidth the stroke width of indicator. * @see CircularIndicatorDefaults.style
* @param strokeCap the stroke cap of indicator.
* @param radius the radius of indicator.
* @param animation the animation of indicator.
*/ */
@Immutable @Immutable
data class CircularIndicatorStyle( data class CircularIndicatorStyle(
override val strokeWidth: Dp, override val strokeWidth: Dp,
override val strokeCap: StrokeCap, override val strokeCap: StrokeCap,
val radius: Dp, val radius: Dp
val animation: CircularIndicatorAnimation
) : ProgressIndicatorStyle ) : ProgressIndicatorStyle
/** /**
* Style defines for linear progress indicator. * Style defines for linear progress indicator.
* @param strokeWidth the stroke width of indicator. * @see LinearIndicatorDefaults.style
* @param strokeCap the stroke cap of indicator.
* @param width the width of indicator.
* @param animation the animation of indicator.
*/ */
@Immutable @Immutable
data class LinearIndicatorStyle( data class LinearIndicatorStyle(
override val strokeWidth: Dp, override val strokeWidth: Dp,
override val strokeCap: StrokeCap, override val strokeCap: StrokeCap,
val width: Dp, val width: Dp
val animation: LinearIndicatorAnimation
) : ProgressIndicatorStyle ) : ProgressIndicatorStyle
/** /**
@@ -147,17 +153,6 @@ data class LinearIndicatorAnimation(
val secondLineTailDelay: Int val secondLineTailDelay: Int
) : ProgressIndicatorAnimation ) : ProgressIndicatorAnimation
/**
* Colors defines for progress indicator.
* @param foregroundColor the foreground color of indicator.
* @param backgroundColor the background color of indicator.
*/
@Immutable
data class ProgressIndicatorColors(
val foregroundColor: Color,
val backgroundColor: Color
)
/** /**
* Flexi UI circular progress indicator. * Flexi UI circular progress indicator.
* @see LinearProgressIndicator * @see LinearProgressIndicator
@@ -166,8 +161,9 @@ data class ProgressIndicatorColors(
* @param min the min of indicator, default is 0f. * @param min the min of indicator, default is 0f.
* @param max the max of indicator, default is 1f. * @param max the max of indicator, default is 1f.
* @param indeterminate the indeterminate of indicator, default is false. * @param indeterminate the indeterminate of indicator, default is false.
* @param colors the colors of indicator, default is [CircularIndicatorDefaults.colors]. * @param animation the animation of indicator.
* @param style the style of indicator, default is [CircularIndicatorDefaults.style]. * @param colors the colors of indicator.
* @param style the style of indicator.
*/ */
@Composable @Composable
fun CircularProgressIndicator( fun CircularProgressIndicator(
@@ -176,11 +172,14 @@ fun CircularProgressIndicator(
min: Float = 0f, min: Float = 0f,
max: Float = 1f, max: Float = 1f,
indeterminate: Boolean = progress < min, indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = CircularIndicatorDefaults.colors, animation: CircularIndicatorAnimation = DefaultCircularIndicatorAnimation,
style: CircularIndicatorStyle = CircularIndicatorDefaults.style colors: ProgressIndicatorColors? = null,
style: CircularIndicatorStyle? = null
) { ) {
val diameter = style.radius * 2 val currentColors = colors ?: LocalProgressIndicatorColors.current ?: CircularIndicatorDefaults.colors()
val stroke = with(LocalDensity.current) { Stroke(width = style.strokeWidth.toPx(), cap = style.strokeCap) } val currentStyle = style ?: LocalProgressIndicatorStyle.current as? CircularIndicatorStyle? ?: CircularIndicatorDefaults.style()
val diameter = currentStyle.radius * 2
val stroke = with(LocalDensity.current) { Stroke(width = currentStyle.strokeWidth.toPx(), cap = currentStyle.strokeCap) }
/** Build determinate progress indicator. */ /** Build determinate progress indicator. */
@Composable @Composable
@@ -190,8 +189,8 @@ fun CircularProgressIndicator(
Canvas(modifier.progressSemantics(normalizedProgress).size(diameter)) { Canvas(modifier.progressSemantics(normalizedProgress).size(diameter)) {
val startAngle = 270f val startAngle = 270f
val sweep = normalizedProgress * 360f val sweep = normalizedProgress * 360f
drawCircularIndicatorBackground(colors.backgroundColor, stroke) drawCircularIndicatorBackground(currentColors.backgroundColor, stroke)
drawCircularIndicator(startAngle, sweep, colors.foregroundColor, stroke) drawCircularIndicator(startAngle, sweep, currentColors.foregroundColor, stroke)
} }
} }
@@ -201,55 +200,55 @@ fun CircularProgressIndicator(
val transition = rememberInfiniteTransition() val transition = rememberInfiniteTransition()
val currentRotation by transition.animateValue( val currentRotation by transition.animateValue(
initialValue = 0, initialValue = 0,
style.animation.rotationsPerCycle, animation.rotationsPerCycle,
Int.VectorConverter, Int.VectorConverter,
infiniteRepeatable( infiniteRepeatable(
animation = tween( animation = tween(
durationMillis = style.animation.duration * style.animation.rotationsPerCycle, durationMillis = animation.duration * animation.rotationsPerCycle,
easing = LinearEasing easing = LinearEasing
) )
) )
) )
val baseRotation by transition.animateFloat( val baseRotation by transition.animateFloat(
initialValue = 0f, initialValue = 0f,
style.animation.baseRotationAngle, animation.baseRotationAngle,
infiniteRepeatable( infiniteRepeatable(
animation = tween( animation = tween(
durationMillis = style.animation.duration, durationMillis = animation.duration,
easing = LinearEasing easing = LinearEasing
) )
) )
) )
val headAndTailAnimationDuration = caleHeadAndTailAnimationDuration(style.animation.duration) val headAndTailAnimationDuration = caleHeadAndTailAnimationDuration(animation.duration)
val endAngle by transition.animateFloat( val endAngle by transition.animateFloat(
initialValue = 0f, initialValue = 0f,
style.animation.jumpRotationAngle, animation.jumpRotationAngle,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2 durationMillis = headAndTailAnimationDuration * 2
0f at 0 with CircularEasing 0f at 0 with CircularEasing
style.animation.jumpRotationAngle at headAndTailAnimationDuration animation.jumpRotationAngle at headAndTailAnimationDuration
} }
) )
) )
val startAngle by transition.animateFloat( val startAngle by transition.animateFloat(
initialValue = 0f, initialValue = 0f,
style.animation.jumpRotationAngle, animation.jumpRotationAngle,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2 durationMillis = headAndTailAnimationDuration * 2
0f at headAndTailAnimationDuration with CircularEasing 0f at headAndTailAnimationDuration with CircularEasing
style.animation.jumpRotationAngle at durationMillis animation.jumpRotationAngle at durationMillis
} }
) )
) )
Canvas(modifier.progressSemantics().size(diameter)) { Canvas(modifier.progressSemantics().size(diameter)) {
drawCircularIndicatorBackground(colors.backgroundColor, stroke) drawCircularIndicatorBackground(currentColors.backgroundColor, stroke)
val rotationAngleOffset = caleRotationAngleOffset(style.animation.baseRotationAngle, style.animation.jumpRotationAngle) val rotationAngleOffset = caleRotationAngleOffset(animation.baseRotationAngle, animation.jumpRotationAngle)
val currentRotationAngleOffset = (currentRotation * rotationAngleOffset) % 360f val currentRotationAngleOffset = (currentRotation * rotationAngleOffset) % 360f
val sweep = abs(endAngle - startAngle) val sweep = abs(endAngle - startAngle)
val offset = style.animation.startAngleOffset + currentRotationAngleOffset + baseRotation val offset = animation.startAngleOffset + currentRotationAngleOffset + baseRotation
drawIndeterminateCircularIndicator(startAngle + offset, style.strokeWidth, diameter, sweep, colors.foregroundColor, stroke) drawIndeterminateCircularIndicator(startAngle + offset, currentStyle.strokeWidth, diameter, sweep, currentColors.foregroundColor, stroke)
} }
} }
if (indeterminate) Indeterminate() else Determinate() if (indeterminate) Indeterminate() else Determinate()
@@ -263,8 +262,9 @@ fun CircularProgressIndicator(
* @param min the min of indicator, default is 0f. * @param min the min of indicator, default is 0f.
* @param max the max of indicator, default is 1f. * @param max the max of indicator, default is 1f.
* @param indeterminate the indeterminate of indicator, default is false. * @param indeterminate the indeterminate of indicator, default is false.
* @param colors the colors of indicator, default is [LinearIndicatorDefaults.colors]. * @param animation the animation of indicator.
* @param style the style of indicator, default is [LinearIndicatorDefaults.style]. * @param colors the colors of indicator.
* @param style the style of indicator.
*/ */
@Composable @Composable
fun LinearProgressIndicator( fun LinearProgressIndicator(
@@ -273,18 +273,22 @@ fun LinearProgressIndicator(
min: Float = 0f, min: Float = 0f,
max: Float = 1f, max: Float = 1f,
indeterminate: Boolean = progress < min, indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = LinearIndicatorDefaults.colors, animation: LinearIndicatorAnimation = DefaultLinearIndicatorAnimation,
style: LinearIndicatorStyle = LinearIndicatorDefaults.style colors: ProgressIndicatorColors? = null,
style: LinearIndicatorStyle? = null
) { ) {
val currentColors = colors ?: LocalProgressIndicatorColors.current ?: CircularIndicatorDefaults.colors()
val currentStyle = style ?: LocalProgressIndicatorStyle.current as? LinearIndicatorStyle? ?: LinearIndicatorDefaults.style()
/** Build determinate progress indicator. */ /** Build determinate progress indicator. */
@Composable @Composable
fun Determinate() { fun Determinate() {
val coercedProgress = progress.coerceIn(min, max) val coercedProgress = progress.coerceIn(min, max)
val normalizedProgress = (coercedProgress - min) / (max - min) val normalizedProgress = (coercedProgress - min) / (max - min)
Canvas(modifier.progressSemantics(normalizedProgress).size(style.width, style.strokeWidth)) { Canvas(modifier.progressSemantics(normalizedProgress).size(currentStyle.width, currentStyle.strokeWidth)) {
val strokeWidth = size.height val strokeWidth = size.height
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap) drawLinearIndicatorBackground(currentColors.backgroundColor, strokeWidth, currentStyle.strokeCap)
drawLinearIndicator(startFraction = 0f, normalizedProgress, colors.foregroundColor, strokeWidth, style.strokeCap) drawLinearIndicator(startFraction = 0f, normalizedProgress, currentColors.foregroundColor, strokeWidth, currentStyle.strokeCap)
} }
} }
@@ -297,9 +301,9 @@ fun LinearProgressIndicator(
targetValue = 1f, targetValue = 1f,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = style.animation.duration durationMillis = animation.duration
0f at style.animation.firstLineHeadDelay with FirstLineHeadEasing 0f at animation.firstLineHeadDelay with FirstLineHeadEasing
1f at style.animation.firstLineHeadDuration + style.animation.firstLineHeadDelay 1f at animation.firstLineHeadDuration + animation.firstLineHeadDelay
} }
) )
) )
@@ -308,9 +312,9 @@ fun LinearProgressIndicator(
targetValue = 1f, targetValue = 1f,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = style.animation.duration durationMillis = animation.duration
0f at style.animation.firstLineTailDelay with FirstLineTailEasing 0f at animation.firstLineTailDelay with FirstLineTailEasing
1f at style.animation.firstLineTailDuration + style.animation.firstLineTailDelay 1f at animation.firstLineTailDuration + animation.firstLineTailDelay
} }
) )
) )
@@ -319,9 +323,9 @@ fun LinearProgressIndicator(
targetValue = 1f, targetValue = 1f,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = style.animation.duration durationMillis = animation.duration
0f at style.animation.secondLineHeadDelay with SecondLineHeadEasing 0f at animation.secondLineHeadDelay with SecondLineHeadEasing
1f at style.animation.secondLineHeadDuration + style.animation.secondLineHeadDelay 1f at animation.secondLineHeadDuration + animation.secondLineHeadDelay
} }
) )
) )
@@ -330,19 +334,19 @@ fun LinearProgressIndicator(
targetValue = 1f, targetValue = 1f,
infiniteRepeatable( infiniteRepeatable(
animation = keyframes { animation = keyframes {
durationMillis = style.animation.duration durationMillis = animation.duration
0f at style.animation.secondLineTailDelay with SecondLineTailEasing 0f at animation.secondLineTailDelay with SecondLineTailEasing
1f at style.animation.secondLineTailDuration + style.animation.secondLineTailDelay 1f at animation.secondLineTailDuration + animation.secondLineTailDelay
} }
) )
) )
Canvas(modifier.progressSemantics().size(style.width, style.strokeWidth)) { Canvas(modifier.progressSemantics().size(currentStyle.width, currentStyle.strokeWidth)) {
val strokeWidth = size.height val strokeWidth = size.height
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap) drawLinearIndicatorBackground(currentColors.backgroundColor, strokeWidth, currentStyle.strokeCap)
if (firstLineHead - firstLineTail > 0) if (firstLineHead - firstLineTail > 0)
drawLinearIndicator(firstLineHead, firstLineTail, colors.foregroundColor, strokeWidth, style.strokeCap) drawLinearIndicator(firstLineHead, firstLineTail, currentColors.foregroundColor, strokeWidth, currentStyle.strokeCap)
if (secondLineHead - secondLineTail > 0) if (secondLineHead - secondLineTail > 0)
drawLinearIndicator(secondLineHead, secondLineTail, colors.foregroundColor, strokeWidth, style.strokeCap) drawLinearIndicator(secondLineHead, secondLineTail, currentColors.foregroundColor, strokeWidth, currentStyle.strokeCap)
} }
} }
if (indeterminate) Indeterminate() else Determinate() if (indeterminate) Indeterminate() else Determinate()
@@ -422,100 +426,107 @@ private fun DrawScope.drawLinearIndicator(
* Defaults of circular progress indicator. * Defaults of circular progress indicator.
*/ */
object CircularIndicatorDefaults { object CircularIndicatorDefaults {
val colors: ProgressIndicatorColors
/**
* Creates a [ProgressIndicatorColors] with the default values.
* @param foregroundColor the foreground color of indicator.
* @param backgroundColor the background color of indicator.
* @return [ProgressIndicatorColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = LocalProgressIndicatorColors.current.copy( foregroundColor: Color = CircularIndicatorProperties.ForegroundColor.toColor(),
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orNull() backgroundColor: Color = CircularIndicatorProperties.BackgroundColor
?: defaultCircularIndicatorColors().foregroundColor, ) = ProgressIndicatorColors(
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orNull() foregroundColor = foregroundColor,
?: defaultCircularIndicatorColors().backgroundColor backgroundColor = backgroundColor
) )
val style: CircularIndicatorStyle
/**
* Creates a [CircularIndicatorStyle] with the default values.
* @param strokeWidth the stroke width of indicator.
* @param strokeCap the stroke cap of indicator.
* @param radius the radius of indicator.
* @return [CircularIndicatorStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultCircularIndicatorStyle() strokeWidth: Dp = CircularIndicatorProperties.StrokeWidth,
strokeCap: StrokeCap = CircularIndicatorProperties._StrokeCap,
radius: Dp = CircularIndicatorProperties.Radius
) = CircularIndicatorStyle(
strokeWidth = strokeWidth,
strokeCap = strokeCap,
radius = radius
)
} }
/** /**
* Defaults of linear progress indicator. * Defaults of linear progress indicator.
*/ */
object LinearIndicatorDefaults { object LinearIndicatorDefaults {
val colors: ProgressIndicatorColors
/**
* Creates a [ProgressIndicatorColors] with the default values.
* @param foregroundColor the foreground color of indicator.
* @param backgroundColor the background color of indicator.
* @return [ProgressIndicatorColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = LocalProgressIndicatorColors.current.copy( foregroundColor: Color = LinearIndicatorProperties.ForegroundColor.toColor(),
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orNull() backgroundColor: Color = LinearIndicatorProperties.BackgroundColor.toColor()
?: defaultLinearIndicatorColors().foregroundColor, ) = ProgressIndicatorColors(
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orNull() foregroundColor = foregroundColor,
?: defaultLinearIndicatorColors().backgroundColor backgroundColor = backgroundColor
) )
val style: LinearIndicatorStyle
/**
* Creates a [LinearIndicatorStyle] with the default values.
* @param strokeWidth the stroke width of indicator.
* @param strokeCap the stroke cap of indicator.
* @param width the width of indicator.
* @return [LinearIndicatorStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultLinearIndicatorStyle() strokeWidth: Dp = LinearIndicatorProperties.StrokeWidth,
strokeCap: StrokeCap = LinearIndicatorProperties._StrokeCap,
width: Dp = LinearIndicatorProperties.Width
) = LinearIndicatorStyle(
strokeWidth = strokeWidth,
strokeCap = strokeCap,
width = width
)
} }
internal val LocalProgressIndicatorColors = compositionLocalOf { DefaultProgressIndicatorColors } @Stable
internal object CircularIndicatorProperties {
val ForegroundColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = Color.Transparent
val StrokeWidth = 4.dp
val _StrokeCap = StrokeCap.Round
val Radius = 20.dp
}
private val DefaultProgressIndicatorColors = ProgressIndicatorColors(Color.Unspecified, Color.Unspecified) @Stable
internal object LinearIndicatorProperties {
val ForegroundColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = ColorsDescriptor.ThemeTertiary
val StrokeWidth = 4.dp
val _StrokeCap = StrokeCap.Round
val Width = 240.dp
}
@Composable internal val LocalProgressIndicatorColors = compositionLocalOf<ProgressIndicatorColors?> { null }
@ReadOnlyComposable internal val LocalProgressIndicatorStyle = compositionLocalOf<ProgressIndicatorStyle?> { null }
private fun defaultCircularIndicatorColors() = ProgressIndicatorColors(
foregroundColor = LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultLinearIndicatorColors() = ProgressIndicatorColors(
foregroundColor = LocalColors.current.themePrimary,
backgroundColor = LocalColors.current.themeTertiary
)
@Composable
@ReadOnlyComposable
private fun defaultCircularIndicatorStyle() = CircularIndicatorStyle(
strokeWidth = DefaultIndicatorStrokeWidth,
strokeCap = StrokeCap.Round,
radius = DefaultCircularIndicatorRadius,
animation = CircularIndicatorAnimation(
duration = DefaultRotationDuration,
rotationsPerCycle = DefaultRotationsPerCycle,
startAngleOffset = DefaultStartAngleOffset,
baseRotationAngle = DefaultBaseRotationAngle,
jumpRotationAngle = DefaultJumpRotationAngle
)
)
@Composable
@ReadOnlyComposable
private fun defaultLinearIndicatorStyle() = LinearIndicatorStyle(
strokeWidth = DefaultIndicatorStrokeWidth,
strokeCap = StrokeCap.Round,
width = DefaultLinearIndicatorWidth,
animation = LinearIndicatorAnimation(
duration = DefaultLinearAnimationDuration,
firstLineHeadDuration = DefaultFirstLineHeadDuration,
firstLineTailDuration = DefaultFirstLineTailDuration,
secondLineHeadDuration = DefaultSecondLineHeadDuration,
secondLineTailDuration = DefaultSecondLineTailDuration,
firstLineHeadDelay = DefaultFirstLineHeadDelay,
firstLineTailDelay = DefaultFirstLineTailDelay,
secondLineHeadDelay = DefaultSecondLineHeadDelay,
secondLineTailDelay = DefaultSecondLineTailDelay
)
)
private fun caleRotationAngleOffset(baseRotationAngle: Float, jumpRotationAngle: Float) = (baseRotationAngle + jumpRotationAngle) % 360f private fun caleRotationAngleOffset(baseRotationAngle: Float, jumpRotationAngle: Float) = (baseRotationAngle + jumpRotationAngle) % 360f
private fun caleHeadAndTailAnimationDuration(rotationDuration: Int) = (rotationDuration * 0.5).toInt() private fun caleHeadAndTailAnimationDuration(rotationDuration: Int) = (rotationDuration * 0.5).toInt()
private val DefaultIndicatorStrokeWidth = 4.dp
private val DefaultLinearIndicatorWidth = 240.dp
private val DefaultCircularIndicatorRadius = 20.dp
private const val DefaultLinearAnimationDuration = 1800 private const val DefaultLinearAnimationDuration = 1800
private const val DefaultRotationAnimationDuration = 1332
private const val DefaultFirstLineHeadDuration = 750 private const val DefaultFirstLineHeadDuration = 750
private const val DefaultFirstLineTailDuration = 850 private const val DefaultFirstLineTailDuration = 850
@@ -528,7 +539,6 @@ private const val DefaultSecondLineHeadDelay = 1000
private const val DefaultSecondLineTailDelay = 1267 private const val DefaultSecondLineTailDelay = 1267
private const val DefaultRotationsPerCycle = 5 private const val DefaultRotationsPerCycle = 5
private const val DefaultRotationDuration = 1332
private const val DefaultStartAngleOffset = -90f private const val DefaultStartAngleOffset = -90f
private const val DefaultBaseRotationAngle = 286f private const val DefaultBaseRotationAngle = 286f
private const val DefaultJumpRotationAngle = 290f private const val DefaultJumpRotationAngle = 290f
@@ -538,3 +548,23 @@ private val FirstLineTailEasing = CubicBezierEasing(0.4f, 0f, 1f, 1f)
private val SecondLineHeadEasing = CubicBezierEasing(0f, 0f, 0.65f, 1f) private val SecondLineHeadEasing = CubicBezierEasing(0f, 0f, 0.65f, 1f)
private val SecondLineTailEasing = CubicBezierEasing(0.1f, 0f, 0.45f, 1f) private val SecondLineTailEasing = CubicBezierEasing(0.1f, 0f, 0.45f, 1f)
private val CircularEasing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f) private val CircularEasing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f)
private val DefaultLinearIndicatorAnimation = LinearIndicatorAnimation(
duration = DefaultLinearAnimationDuration,
firstLineHeadDuration = DefaultFirstLineHeadDuration,
firstLineTailDuration = DefaultFirstLineTailDuration,
secondLineHeadDuration = DefaultSecondLineHeadDuration,
secondLineTailDuration = DefaultSecondLineTailDuration,
firstLineHeadDelay = DefaultFirstLineHeadDelay,
firstLineTailDelay = DefaultFirstLineTailDelay,
secondLineHeadDelay = DefaultSecondLineHeadDelay,
secondLineTailDelay = DefaultSecondLineTailDelay
)
private val DefaultCircularIndicatorAnimation = CircularIndicatorAnimation(
duration = DefaultRotationAnimationDuration,
rotationsPerCycle = DefaultRotationsPerCycle,
startAngleOffset = DefaultStartAngleOffset,
baseRotationAngle = DefaultBaseRotationAngle,
jumpRotationAngle = DefaultJumpRotationAngle
)

View File

@@ -19,14 +19,13 @@
* *
* This file is created by fankes on 2023/11/9. * This file is created by fankes on 2023/11/9.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.interaction.collectIsHoveredAsState
@@ -39,7 +38,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -54,32 +53,28 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.clickable import com.highcapable.betterandroid.compose.extension.ui.clickable
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
/** /**
* Colors defines for radio button. * Colors defines for radio button.
* @param contentColor the color of the check mark. * @see RadioButtonDefaults.colors
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
*/ */
@Immutable @Immutable
data class RadioButtonColors( data class RadioButtonColors(
val contentColor: Color, val contentColor: Color,
val inactiveColor: Color, val inactiveColor: Color,
val activeColor: Color val activeColor: Color,
val borderColor: Color
) )
/** /**
* Style defines for radio button. * Style defines for radio button.
* @param contentSpacing the spacing between the check mark and the content. * @see RadioButtonDefaults.style
* @param contentRadius the radius of the check mark.
* @param strokeRadius the radius of the box.
* @param pressedGain the gain when pressed.
* @param hoveredGain the gain when hovered.
* @param shape the shape.
* @param border the border stroke.
*/ */
@Immutable @Immutable
data class RadioButtonStyle( data class RadioButtonStyle(
@@ -90,7 +85,7 @@ data class RadioButtonStyle(
val pressedGain: Float, val pressedGain: Float,
val hoveredGain: Float, val hoveredGain: Float,
val shape: Shape, val shape: Shape,
val border: BorderStroke val borderWidth: Dp
) )
/** /**
@@ -109,8 +104,8 @@ fun RadioButton(
selected: Boolean, selected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: RadioButtonColors = RadioButtonDefaults.colors, colors: RadioButtonColors = RadioButtonDefaults.colors(),
style: RadioButtonStyle = RadioButtonDefaults.style, style: RadioButtonStyle = RadioButtonDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null content: @Composable (RowScope.() -> Unit)? = null
@@ -157,45 +152,73 @@ fun RadioButton(
* Defaults of radio button. * Defaults of radio button.
*/ */
object RadioButtonDefaults { object RadioButtonDefaults {
val colors: RadioButtonColors
/**
* Creates a [RadioButtonColors] with the default values.
* @param contentColor the color of the check mark.
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
* @param borderColor the color of the border.
* @return [RadioButtonColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultRadioButtonColors() contentColor: Color = RadioButtonProperties.ContentColor,
val style: RadioButtonStyle inactiveColor: Color = RadioButtonProperties.InactiveColor.toColor(),
activeColor: Color = RadioButtonProperties.ActiveColor.toColor(),
borderColor: Color = RadioButtonProperties.BorderColor.toColor()
) = RadioButtonColors(
contentColor = contentColor,
inactiveColor = inactiveColor,
activeColor = activeColor,
borderColor = borderColor
)
/**
* Creates a [RadioButtonStyle] with the default values.
* @param contentSpacing the spacing between the check mark and the content.
* @param contentRadius the radius of the check mark.
* @param strokeRadius the radius of the box.
* @param pressedGain the gain when pressed.
* @param hoveredGain the gain when hovered.
* @param shape the shape.
* @param borderWidth the border width.
* @return [RadioButtonStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultRadioButtonStyle() contentSpacing: Dp = RadioButtonProperties.ContentSpacing.toDp(),
contentRadius: Dp = RadioButtonProperties.ContentRadius,
contentShadowSize: Dp = RadioButtonProperties.ContentShadowSize,
strokeRadius: Dp = RadioButtonProperties.StrokeRadius,
pressedGain: Float = RadioButtonProperties.PressedGain,
hoveredGain: Float = RadioButtonProperties.HoveredGain,
shape: Shape = RadioButtonProperties.Shape.toShape(),
borderWidth: Dp = RadioButtonProperties.BorderWidth.toDp()
) = RadioButtonStyle(
contentSpacing = contentSpacing,
contentRadius = contentRadius,
contentShadowSize = contentShadowSize,
strokeRadius = strokeRadius,
pressedGain = pressedGain,
hoveredGain = hoveredGain,
shape = shape,
borderWidth = borderWidth
)
} }
@Composable @Stable
@ReadOnlyComposable internal object RadioButtonProperties {
private fun defaultRadioButtonColors() = RadioButtonColors( val ContentColor = Color.White
contentColor = Color.White, val InactiveColor = ColorsDescriptor.ThemeTertiary
inactiveColor = LocalColors.current.themeTertiary, val ActiveColor = ColorsDescriptor.ThemePrimary
activeColor = LocalColors.current.themePrimary val BorderColor = ColorsDescriptor.TextPrimary
) val ContentSpacing = SizesDescriptor.SpacingSecondary
val ContentRadius = 5.dp
@Composable val ContentShadowSize = 0.5.dp
@ReadOnlyComposable val StrokeRadius = 10.dp
private fun defaultRadioButtonStyle() = RadioButtonStyle( const val PressedGain = 0.9f
contentSpacing = LocalSizes.current.spacingSecondary, const val HoveredGain = 1.2f
contentRadius = DefaultContentRadius, val Shape = ShapesDescriptor.Tertiary
contentShadowSize = DefaultContentShadowSize, val BorderWidth = SizesDescriptor.BorderSizeTertiary
strokeRadius = DefaultStrokeRadius, }
pressedGain = DefaultPressedGain,
hoveredGain = DefaultHoveredGain,
shape = LocalShapes.current.tertiary,
border = defaultRadioButtonBorder()
)
@Composable
@ReadOnlyComposable
private fun defaultRadioButtonBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
private val DefaultContentRadius = 5.dp
private val DefaultStrokeRadius = 10.dp
private const val DefaultPressedGain = 0.9f
private const val DefaultHoveredGain = 1.2f
private val DefaultContentShadowSize = 0.5.dp

View File

@@ -58,7 +58,7 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
@Composable @Composable
fun Scaffold( fun Scaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: SurfaceColors = SurfaceDefaults.colors, colors: SurfaceColors = SurfaceDefaults.colors(),
padding: ComponentPadding = SurfaceDefaults.padding, padding: ComponentPadding = SurfaceDefaults.padding,
verticalArrangement: Arrangement.Vertical = Arrangement.Top, verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start, horizontalAlignment: Alignment.Horizontal = Alignment.Start,

View File

@@ -19,12 +19,11 @@
* *
* This file is created by fankes on 2023/11/9. * This file is created by fankes on 2023/11/9.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
@@ -42,7 +41,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -63,40 +62,33 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
* Colors defines for slider. * Colors defines for slider.
* @param trackInactiveColor the inactive color of track. * @see SliderDefaults.colors
* @param trackActiveColor the active color of track.
* @param thumbColor the color of thumb.
* @param stepColor the color of step.
*/ */
@Immutable @Immutable
data class SliderColors( data class SliderColors(
val trackInactiveColor: Color,
val trackActiveColor: Color,
val thumbColor: Color, val thumbColor: Color,
val stepColor: Color val stepColor: Color,
val thumbBorderColor: Color,
val stepBorderColor: Color,
val trackBorderColor: Color,
val trackInactiveColor: Color,
val trackActiveColor: Color
) )
/** /**
* Style defines for slider. * Style defines for slider.
* @param thumbRadius the radius of thumb. * @see SliderDefaults.style
* @param thumbGain the gain of thumb.
* @param thumbShadowSize the shadow size of thumb.
* @param thumbShape the shape of thumb.
* @param stepShape the shape of step.
* @param trackShape the shape of track.
* @param thumbBorder the border of thumb.
* @param stepBorder the border of step.
* @param trackBorder the border of track.
* @param trackWidth the width of track.
* @param trackHeight the height of track.
*/ */
@Immutable @Immutable
data class SliderStyle( data class SliderStyle(
@@ -106,11 +98,11 @@ data class SliderStyle(
val thumbShape: Shape, val thumbShape: Shape,
val stepShape: Shape, val stepShape: Shape,
val trackShape: Shape, val trackShape: Shape,
val thumbBorder: BorderStroke,
val stepBorder: BorderStroke,
val trackBorder: BorderStroke,
val trackWidth: Dp, val trackWidth: Dp,
val trackHeight: Dp val trackHeight: Dp,
val thumbBorderWidth: Dp,
val stepBorderWidth: Dp,
val trackBorderWidth: Dp
) )
/** /**
@@ -132,8 +124,8 @@ fun Slider(
value: Float, value: Float,
onValueChange: (Float) -> Unit, onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: SliderColors = SliderDefaults.colors, colors: SliderColors = SliderDefaults.colors(),
style: SliderStyle = SliderDefaults.style, style: SliderStyle = SliderDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
min: Float = 0f, min: Float = 0f,
max: Float = 1f, max: Float = 1f,
@@ -181,7 +173,7 @@ fun Slider(
Box( Box(
modifier = Modifier.size(trackAdoptWidth, style.trackHeight) modifier = Modifier.size(trackAdoptWidth, style.trackHeight)
.background(colors.trackInactiveColor, style.trackShape) .background(colors.trackInactiveColor, style.trackShape)
.borderOrElse(style.trackBorder, style.trackShape) .borderOrElse(style.trackBorderWidth, colors.trackBorderColor, style.trackShape)
.drawWithContent { .drawWithContent {
drawRoundRect( drawRoundRect(
color = colors.trackActiveColor, color = colors.trackActiveColor,
@@ -206,7 +198,7 @@ fun Slider(
Box( Box(
modifier = Modifier.size(style.trackHeight) modifier = Modifier.size(style.trackHeight)
.background(colors.stepColor, style.stepShape) .background(colors.stepColor, style.stepShape)
.borderOrElse(style.stepBorder, style.stepShape) .borderOrElse(style.stepBorderWidth, colors.stepBorderColor, style.stepShape)
) )
} }
} }
@@ -220,7 +212,7 @@ fun Slider(
.scale(animatedScale) .scale(animatedScale)
.shadow(style.thumbShadowSize, style.thumbShape) .shadow(style.thumbShadowSize, style.thumbShape)
.background(colors.thumbColor, style.thumbShape) .background(colors.thumbColor, style.thumbShape)
.borderOrElse(style.thumbBorder, style.thumbShape) .borderOrElse(style.thumbBorderWidth, colors.thumbBorderColor, style.thumbShape)
.draggable( .draggable(
orientation = Orientation.Horizontal, orientation = Orientation.Horizontal,
state = rememberDraggableState { delta -> state = rememberDraggableState { delta ->
@@ -275,48 +267,98 @@ fun Slider(
* Defaults of slider. * Defaults of slider.
*/ */
object SliderDefaults { object SliderDefaults {
val colors
/**
* Creates a [SliderColors] with the default values.
* @param thumbColor the color of thumb.
* @param stepColor the color of step.
* @param thumbBorderColor the border color of thumb.
* @param stepBorderColor the border color of step.
* @param trackBorderColor the border color of track.
* @param trackInactiveColor the inactive color of track.
* @param trackActiveColor the active color of track.
* @return [SliderColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultSliderColors() thumbColor: Color = SliderProperties.ThumbColor.toColor(),
val style stepColor: Color = SliderProperties.StepColor.toColor(),
thumbBorderColor: Color = SliderProperties.ThumbBorderColor.toColor(),
stepBorderColor: Color = SliderProperties.StepBorderColor.toColor(),
trackBorderColor: Color = SliderProperties.TrackBorderColor.toColor(),
trackInactiveColor: Color = SliderProperties.TrackInactiveColor.toColor(),
trackActiveColor: Color = SliderProperties.TrackActiveColor.toColor()
) = SliderColors(
thumbColor = thumbColor,
stepColor = stepColor,
thumbBorderColor = thumbBorderColor,
stepBorderColor = stepBorderColor,
trackBorderColor = trackBorderColor,
trackInactiveColor = trackInactiveColor,
trackActiveColor = trackActiveColor
)
/**
* Creates a [SliderStyle] with the default values.
* @param thumbRadius the radius of thumb.
* @param thumbGain the gain of thumb.
* @param thumbShadowSize the shadow size of thumb.
* @param thumbShape the shape of thumb.
* @param stepShape the shape of step.
* @param trackShape the shape of track.
* @param trackWidth the width of track.
* @param trackHeight the height of track.
* @param thumbBorderWidth the border width of thumb.
* @param stepBorderWidth the border width of step.
* @param trackBorderWidth the border width of track.
* @return [SliderStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultSliderStyle() thumbRadius: Dp = SliderProperties.ThumbRadius,
thumbGain: Float = SliderProperties.ThumbGain,
thumbShadowSize: Dp = SliderProperties.ThumbShadowSize,
thumbShape: Shape = SliderProperties.ThumbShape.toShape(),
stepShape: Shape = SliderProperties.StepShape.toShape(),
trackShape: Shape = SliderProperties.TrackShape.toShape(),
trackWidth: Dp = SliderProperties.TrackWidth,
trackHeight: Dp = SliderProperties.TrackHeight,
thumbBorderWidth: Dp = SliderProperties.ThumbBorderWidth.toDp(),
stepBorderWidth: Dp = SliderProperties.StepBorderWidth.toDp(),
trackBorderWidth: Dp = SliderProperties.TrackBorderWidth.toDp()
) = SliderStyle(
thumbRadius = thumbRadius,
thumbGain = thumbGain,
thumbShadowSize = thumbShadowSize,
thumbShape = thumbShape,
stepShape = stepShape,
trackShape = trackShape,
trackWidth = trackWidth,
trackHeight = trackHeight,
thumbBorderWidth = thumbBorderWidth,
stepBorderWidth = stepBorderWidth,
trackBorderWidth = trackBorderWidth
)
} }
@Composable @Stable
@ReadOnlyComposable internal object SliderProperties {
private fun defaultSliderColors() = SliderColors( val ThumbColor = ColorsDescriptor.ThemePrimary
trackInactiveColor = LocalColors.current.themeTertiary, val StepColor = ColorsDescriptor.ThemeSecondary
trackActiveColor = LocalColors.current.themePrimary, val ThumbBorderColor = ColorsDescriptor.TextPrimary
thumbColor = LocalColors.current.themePrimary, val StepBorderColor = ColorsDescriptor.TextPrimary
stepColor = LocalColors.current.themeSecondary val TrackBorderColor = ColorsDescriptor.TextPrimary
) val TrackInactiveColor = ColorsDescriptor.ThemeTertiary
val TrackActiveColor = ColorsDescriptor.ThemePrimary
@Composable val ThumbRadius = 10.dp
@ReadOnlyComposable const val ThumbGain = 1.1f
private fun defaultSliderStyle() = SliderStyle( val ThumbShadowSize = 0.5.dp
thumbRadius = DefaultThumbRadius, val ThumbShape = ShapesDescriptor.Tertiary
thumbGain = DefaultThumbGain, val StepShape = ShapesDescriptor.Tertiary
thumbShadowSize = DefaultThumbShadowSize, val TrackShape = ShapesDescriptor.Primary
thumbShape = LocalShapes.current.tertiary, val TrackWidth = 240.dp
stepShape = LocalShapes.current.tertiary, val TrackHeight = 4.dp
trackShape = LocalShapes.current.primary, val ThumbBorderWidth = SizesDescriptor.BorderSizeTertiary
thumbBorder = defaultSliderBorder(), val StepBorderWidth = SizesDescriptor.BorderSizeTertiary
stepBorder = defaultSliderBorder(), val TrackBorderWidth = SizesDescriptor.BorderSizeTertiary
trackBorder = defaultSliderBorder(), }
trackWidth = DefaultTrackWidth,
trackHeight = DefaultTrackHeight
)
@Composable
@ReadOnlyComposable
private fun defaultSliderBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
private val DefaultThumbRadius = 10.dp
private const val DefaultThumbGain = 1.1f
private val DefaultThumbShadowSize = 0.5.dp
private val DefaultTrackWidth = 240.dp
private val DefaultTrackHeight = 4.dp

View File

@@ -31,19 +31,22 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.platform.debugInspectorInfo
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
/** /**
* Colors defines for surface. * Colors defines for surface.
* @param contentColor the content color, usually for the text color. * @see SurfaceDefaults.colors
* @param backgroundColor the background color.
*/ */
@Immutable @Immutable
data class SurfaceColors( data class SurfaceColors(
@@ -65,7 +68,7 @@ data class SurfaceColors(
fun Surface( fun Surface(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
initializer: @Composable Modifier.() -> Modifier = { Modifier }, initializer: @Composable Modifier.() -> Modifier = { Modifier },
colors: SurfaceColors = SurfaceDefaults.colors, colors: SurfaceColors = SurfaceDefaults.colors(),
padding: ComponentPadding = SurfaceDefaults.padding, padding: ComponentPadding = SurfaceDefaults.padding,
content: @Composable BoxScope.() -> Unit content: @Composable BoxScope.() -> Unit
) { ) {
@@ -100,25 +103,34 @@ private fun Modifier.surface(
* Defaults of surface. * Defaults of surface.
*/ */
object SurfaceDefaults { object SurfaceDefaults {
val colors: SurfaceColors
/**
* Creates a [SurfaceColors] with the default values.
* @param contentColor the content color, usually for the text color.
* @param backgroundColor the background color.
* @return [SurfaceColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultSurfaceColors() contentColor: Color = SurfaceProperties.ContentColor.toColor(),
backgroundColor: Color = SurfaceProperties.BackgroundColor.toColor()
) = SurfaceColors(contentColor, backgroundColor)
/**
* Returns the default padding of surface.
* @return [ComponentPadding]
*/
val padding: ComponentPadding val padding: ComponentPadding
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = defaultSurfacePadding() get() = SurfaceProperties.Padding.toPadding()
}
@Stable
internal object SurfaceProperties {
val ContentColor = ColorsDescriptor.TextPrimary
val BackgroundColor = ColorsDescriptor.BackgroundPrimary
val Padding = PaddingDescriptor(SizesDescriptor.SpacingPrimary)
} }
internal val LocalInSurface = compositionLocalOf { false } internal val LocalInSurface = compositionLocalOf { false }
@Composable
@ReadOnlyComposable
private fun defaultSurfaceColors() = SurfaceColors(
contentColor = LocalColors.current.textPrimary,
backgroundColor = LocalColors.current.backgroundPrimary
)
@Composable
@ReadOnlyComposable
private fun defaultSurfacePadding() = ComponentPadding(LocalSizes.current.spacingPrimary)

View File

@@ -19,13 +19,12 @@
* *
* This file is created by fankes on 2023/11/9. * This file is created by fankes on 2023/11/9.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.draggable
@@ -41,7 +40,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -62,37 +61,30 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.clickable import com.highcapable.betterandroid.compose.extension.ui.clickable
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
* Colors defines for switch. * Colors defines for switch.
* @param thumbColor the color of thumb. * @see SwitchDefaults.colors
* @param trackInactive the color of track when switch is inactive.
* @param trackActive the color of track when switch is active.
*/ */
@Immutable @Immutable
data class SwitchColors( data class SwitchColors(
val thumbColor: Color, val thumbColor: Color,
val trackInactive: Color, val thumbBorderColor: Color,
val trackActive: Color val trackBorderColor: Color,
val trackInactiveColor: Color,
val trackActiveColor: Color
) )
/** /**
* Style defines for switch. * Style defines for switch.
* @param padding the padding between thumb and track. * @see SwitchDefaults.style
* @param contentSpacing the spacing between content and track.
* @param thumbRadius the radius of thumb.
* @param thumbGain the gain of thumb when switch is hovered or dragging.
* @param thumbShadowSize the shadow size of thumb.
* @param thumbShape the shape of thumb.
* @param trackShape the shape of track.
* @param thumbBorder the border of thumb.
* @param trackBorder the border of track.
* @param trackWidth the width of track.
* @param trackHeight the height of track.
*/ */
@Immutable @Immutable
data class SwitchStyle( data class SwitchStyle(
@@ -103,10 +95,10 @@ data class SwitchStyle(
val thumbShadowSize: Dp, val thumbShadowSize: Dp,
val thumbShape: Shape, val thumbShape: Shape,
val trackShape: Shape, val trackShape: Shape,
val thumbBorder: BorderStroke,
val trackBorder: BorderStroke,
val trackWidth: Dp, val trackWidth: Dp,
val trackHeight: Dp val trackHeight: Dp,
val thumbBorderWidth: Dp,
val trackBorderWidth: Dp
) )
/** /**
@@ -125,8 +117,8 @@ fun Switch(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: SwitchColors = SwitchDefaults.colors, colors: SwitchColors = SwitchDefaults.colors(),
style: SwitchStyle = SwitchDefaults.style, style: SwitchStyle = SwitchDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null content: @Composable (RowScope.() -> Unit)? = null
@@ -142,12 +134,12 @@ fun Switch(
if (!hovered && !dragging) offsetX = if (checked) maxOffsetX else 0f if (!hovered && !dragging) offsetX = if (checked) maxOffsetX else 0f
val animatedOffsetX by animateFloatAsState(offsetX) val animatedOffsetX by animateFloatAsState(offsetX)
val animatedScale by animateFloatAsState(if (hovered || dragging) style.thumbGain else 1f) val animatedScale by animateFloatAsState(if (hovered || dragging) style.thumbGain else 1f)
var trackColor by remember { mutableStateOf(colors.trackInactive) } var trackColor by remember { mutableStateOf(colors.trackInactiveColor) }
/** Update the track color of switch. */ /** Update the track color of switch. */
fun updateTrackColor() { fun updateTrackColor() {
val fraction = (offsetX / maxOffsetX).coerceIn(0f, 1f) val fraction = (offsetX / maxOffsetX).coerceIn(0f, 1f)
trackColor = lerp(colors.trackInactive, colors.trackActive, fraction) trackColor = lerp(colors.trackInactiveColor, colors.trackActiveColor, fraction)
} }
updateTrackColor() updateTrackColor()
val animatedTrackColor by animateColorAsState(trackColor) val animatedTrackColor by animateColorAsState(trackColor)
@@ -166,7 +158,7 @@ fun Switch(
offsetX = if (checked) 0f else maxOffsetX offsetX = if (checked) 0f else maxOffsetX
onCheckedChange(!checked) onCheckedChange(!checked)
}.background(if (efficientDragging) trackColor else animatedTrackColor, style.trackShape) }.background(if (efficientDragging) trackColor else animatedTrackColor, style.trackShape)
.borderOrElse(style.trackBorder, style.trackShape) .borderOrElse(style.trackBorderWidth, colors.trackBorderColor, style.trackShape)
.size(style.trackWidth, style.trackHeight) .size(style.trackWidth, style.trackHeight)
.padding(style.padding), .padding(style.padding),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -183,7 +175,7 @@ fun Switch(
.scale(animatedScale) .scale(animatedScale)
.shadow(style.thumbShadowSize, style.thumbShape) .shadow(style.thumbShadowSize, style.thumbShape)
.background(colors.thumbColor, style.thumbShape) .background(colors.thumbColor, style.thumbShape)
.borderOrElse(style.thumbBorder, style.thumbShape) .borderOrElse(style.thumbBorderWidth, colors.thumbBorderColor, style.thumbShape)
.draggable( .draggable(
enabled = enabled, enabled = enabled,
orientation = Orientation.Horizontal, orientation = Orientation.Horizontal,
@@ -232,49 +224,93 @@ fun Switch(
* Defaults of switch. * Defaults of switch.
*/ */
object SwitchDefaults { object SwitchDefaults {
val colors: SwitchColors
/**
* Creates a [SwitchColors] with the default values.
* @param thumbColor the color of thumb.
* @param thumbBorderColor the border color of thumb.
* @param trackBorderColor the border color of track.
* @param trackInactiveColor the color of track when switch is inactive.
* @param trackActiveColor the color of track when switch is active.
* @return [SwitchColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultSwitchColors() thumbColor: Color = SwitchProperties.ThumbColor,
val style: SwitchStyle thumbBorderColor: Color = SwitchProperties.ThumbBorderColor.toColor(),
trackBorderColor: Color = SwitchProperties.TrackBorderColor.toColor(),
trackInactiveColor: Color = SwitchProperties.TrackInactiveColor.toColor(),
trackActiveColor: Color = SwitchProperties.TrackActiveColor.toColor()
) = SwitchColors(
thumbColor = thumbColor,
thumbBorderColor = thumbBorderColor,
trackBorderColor = trackBorderColor,
trackInactiveColor = trackInactiveColor,
trackActiveColor = trackActiveColor
)
/**
* Creates a [SwitchStyle] with the default values.
* @param padding the padding between thumb and track.
* @param contentSpacing the spacing between content and track.
* @param thumbRadius the radius of thumb.
* @param thumbGain the gain of thumb when switch is hovered or dragging.
* @param thumbShadowSize the shadow size of thumb.
* @param thumbShape the shape of thumb.
* @param trackShape the shape of track.
* @param trackWidth the width of track.
* @param trackHeight the height of track.
* @param thumbBorderWidth the border width of thumb.
* @param trackBorderWidth the border width of track.
* @return [SwitchStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultSwitchStyle() padding: ComponentPadding = SwitchProperties.Padding,
contentSpacing: Dp = SwitchProperties.ContentSpacing.toDp(),
thumbRadius: Dp = SwitchProperties.ThumbRadius,
thumbGain: Float = SwitchProperties.ThumbGain,
thumbShadowSize: Dp = SwitchProperties.ThumbShadowSize,
thumbShape: Shape = SwitchProperties.ThumbShape.toShape(),
trackShape: Shape = SwitchProperties.TrackShape.toShape(),
trackWidth: Dp = SwitchProperties.TrackWidth,
trackHeight: Dp = SwitchProperties.TrackHeight,
trackBorderWidth: Dp = SwitchProperties.TrackBorderWidth.toDp(),
thumbBorderWidth: Dp = SwitchProperties.ThumbBorderWidth.toDp()
) = SwitchStyle(
padding = padding,
contentSpacing = contentSpacing,
thumbRadius = thumbRadius,
thumbGain = thumbGain,
thumbShadowSize = thumbShadowSize,
thumbShape = thumbShape,
trackShape = trackShape,
trackWidth = trackWidth,
trackHeight = trackHeight,
thumbBorderWidth = thumbBorderWidth,
trackBorderWidth = trackBorderWidth
)
} }
@Composable /**
@ReadOnlyComposable * Properties for [Switch].
private fun defaultSwitchColors() = SwitchColors( */
thumbColor = Color.White, @Stable
trackInactive = LocalColors.current.themeTertiary, internal object SwitchProperties {
trackActive = LocalColors.current.themePrimary val ThumbColor = Color.White
) val ThumbBorderColor = ColorsDescriptor.TextPrimary
val TrackBorderColor = ColorsDescriptor.TextPrimary
@Composable val TrackInactiveColor = ColorsDescriptor.ThemeTertiary
@ReadOnlyComposable val TrackActiveColor = ColorsDescriptor.ThemePrimary
private fun defaultSwitchStyle() = SwitchStyle( val Padding = ComponentPadding(horizontal = 3.5.dp)
padding = ComponentPadding(horizontal = DefaultSwitchPadding), val ContentSpacing = SizesDescriptor.SpacingSecondary
contentSpacing = LocalSizes.current.spacingSecondary, val ThumbRadius = 6.5.dp
thumbRadius = DefaultThumbRadius, const val ThumbGain = 1.1f
thumbGain = DefaultThumbGain, val ThumbShadowSize = 0.5.dp
thumbShadowSize = DefaultThumbShadowSize, val ThumbShape = ShapesDescriptor.Tertiary
thumbShape = LocalShapes.current.tertiary, val TrackShape = ShapesDescriptor.Tertiary
trackShape = LocalShapes.current.tertiary, val TrackWidth = 40.dp
thumbBorder = defaultSwitchBorder(), val TrackHeight = 20.dp
trackBorder = defaultSwitchBorder(), val ThumbBorderWidth = SizesDescriptor.BorderSizeTertiary
trackWidth = DefaultTrackWidth, val TrackBorderWidth = SizesDescriptor.BorderSizeTertiary
trackHeight = DefaultTrackHeight }
)
@Composable
@ReadOnlyComposable
private fun defaultSwitchBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
private val DefaultSwitchPadding = 3.5.dp
private val DefaultThumbRadius = 6.5.dp
private const val DefaultThumbGain = 1.1f
private val DefaultThumbShadowSize = 0.5.dp
private val DefaultTrackWidth = 40.dp
private val DefaultTrackHeight = 20.dp

View File

@@ -33,7 +33,6 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -48,7 +47,6 @@ import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -73,18 +71,19 @@ import androidx.compose.ui.unit.lerp
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toShape
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* Colors defines for tab. * Colors defines for tab.
* @param indicatorColor the indicator color. * @see TabDefaults.colors
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
*/ */
@Immutable @Immutable
data class TabColors( data class TabColors(
@@ -95,11 +94,7 @@ data class TabColors(
/** /**
* Style defines for tab. * Style defines for tab.
* @param contentPadding the content padding. * @see TabDefaults.style
* @param contentShape the content shape.
* @param indicatorWidth the indicator width.
* @param indicatorHeight the indicator height.
* @param indicatorShape the indicator shape.
*/ */
@Immutable @Immutable
data class TabStyle( data class TabStyle(
@@ -125,8 +120,8 @@ data class TabStyle(
fun TabRow( fun TabRow(
selectedTabIndex: Int = 0, selectedTabIndex: Int = 0,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TabColors = TabDefaults.colors, colors: TabColors = TabDefaults.colors(),
style: TabStyle = TabDefaults.style, style: TabStyle = TabDefaults.style(),
indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) }, indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) },
tabs: @Composable () -> Unit tabs: @Composable () -> Unit
) { ) {
@@ -177,8 +172,8 @@ fun TabRow(
fun ScrollableTabRow( fun ScrollableTabRow(
selectedTabIndex: Int = 0, selectedTabIndex: Int = 0,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TabColors = TabDefaults.colors, colors: TabColors = TabDefaults.colors(),
style: TabStyle = TabDefaults.style, style: TabStyle = TabDefaults.style(),
scrollState: ScrollState = rememberScrollState(), scrollState: ScrollState = rememberScrollState(),
indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) }, indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) },
tabs: @Composable () -> Unit tabs: @Composable () -> Unit
@@ -249,18 +244,18 @@ fun Tab(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
selectedContentColor: Color = Color.Unspecified, selectedContentColor: Color = Color.Unspecified,
unselectedContentColor: Color = Color.Unspecified, unselectedContentColor: Color = Color.Unspecified,
contentPadding: PaddingValues? = null, contentPadding: ComponentPadding? = null,
contentShape: Shape? = null, contentShape: Shape? = null,
enabled: Boolean = true, enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit content: @Composable RowScope.() -> Unit
) { ) {
val currentSelectedContentColor = selectedContentColor.orNull() val currentSelectedContentColor = selectedContentColor.orNull()
?: LocalTabSelectedContentColor.current.orNull() ?: TabDefaults.colors.selectedContentColor ?: LocalTabSelectedContentColor.current.orNull() ?: TabDefaults.colors().selectedContentColor
val currentUnselectedContentColor = unselectedContentColor.orNull() val currentUnselectedContentColor = unselectedContentColor.orNull()
?: LocalTabUnselectedContentColor.current.orNull() ?: TabDefaults.colors.unselectedContentColor ?: LocalTabUnselectedContentColor.current.orNull() ?: TabDefaults.colors().unselectedContentColor
val currentContentPadding = contentPadding ?: LocalTabContentPadding.current ?: TabDefaults.style.contentPadding val currentContentPadding = contentPadding ?: LocalTabContentPadding.current ?: TabDefaults.style().contentPadding
val currentContentShape = contentShape ?: LocalTabContentShape.current ?: TabDefaults.style.contentShape val currentContentShape = contentShape ?: LocalTabContentShape.current ?: TabDefaults.style().contentShape
val contentColor by animateColorAsState(if (selected) currentSelectedContentColor else currentUnselectedContentColor) val contentColor by animateColorAsState(if (selected) currentSelectedContentColor else currentUnselectedContentColor)
val contentIconStyle = LocalIconStyle.current.copy(tint = contentColor) val contentIconStyle = LocalIconStyle.current.copy(tint = contentColor)
val contentTextStyle = LocalTextStyle.current.copy(color = contentColor) val contentTextStyle = LocalTextStyle.current.copy(color = contentColor)
@@ -508,45 +503,68 @@ private enum class TabSlots { Tabs, TabsAverage, Indicator }
* Defaults of tab. * Defaults of tab.
*/ */
object TabDefaults { object TabDefaults {
val colors: TabColors
/**
* Creates a [TabColors] with the default values.
* @param indicatorColor the indicator color.
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
* @return [TabColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultTabColors() indicatorColor: Color = TabProperties.IndicatorColor.toColor(),
val style: TabStyle selectedContentColor: Color = TabProperties.SelectedContentColor.toColor(),
unselectedContentColor: Color = TabProperties.UnselectedContentColor.toColor()
) = TabColors(
indicatorColor = indicatorColor,
selectedContentColor = selectedContentColor,
unselectedContentColor = unselectedContentColor
)
/**
* Creates a [TabStyle] with the default values.
* @param contentPadding the content padding.
* @param contentShape the content shape.
* @param indicatorWidth the indicator width.
* @param indicatorHeight the indicator height.
* @param indicatorShape the indicator shape.
* @return [TabStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultTabStyle() contentPadding: ComponentPadding = TabProperties.ContentPadding.toPadding(),
contentShape: Shape = AreaBoxDefaults.childShape(),
indicatorWidth: Dp = TabProperties.IndicatorWidth,
indicatorHeight: Dp = TabProperties.IndicatorHeight,
indicatorShape: Shape = TabProperties.IndicatorShape.toShape()
) = TabStyle(
contentPadding = contentPadding,
contentShape = contentShape,
indicatorWidth = indicatorWidth,
indicatorHeight = indicatorHeight,
indicatorShape = indicatorShape
)
}
@Stable
internal object TabProperties {
val IndicatorColor = ColorsDescriptor.ThemePrimary
val SelectedContentColor = ColorsDescriptor.ThemePrimary
val UnselectedContentColor = ColorsDescriptor.TextSecondary
val ContentPadding = PaddingDescriptor(
horizontal = SizesDescriptor.SpacingPrimary,
vertical = SizesDescriptor.SpacingSecondary
)
val IndicatorWidth = Dp.Unspecified
val IndicatorHeight = 3.dp
val IndicatorShape = ShapesDescriptor.Tertiary
} }
private val LocalTabSelectedContentColor = compositionLocalOf { Color.Unspecified } private val LocalTabSelectedContentColor = compositionLocalOf { Color.Unspecified }
private val LocalTabUnselectedContentColor = compositionLocalOf { Color.Unspecified } private val LocalTabUnselectedContentColor = compositionLocalOf { Color.Unspecified }
private val LocalTabContentPadding = compositionLocalOf<PaddingValues?> { null } private val LocalTabContentPadding = compositionLocalOf<ComponentPadding?> { null }
private val LocalTabContentShape = compositionLocalOf<Shape?> { null } private val LocalTabContentShape = compositionLocalOf<Shape?> { null }
@Composable
@ReadOnlyComposable
private fun defaultTabColors() = TabColors(
indicatorColor = LocalColors.current.themePrimary,
selectedContentColor = LocalColors.current.themePrimary,
unselectedContentColor = LocalColors.current.textSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultTabStyle() = TabStyle(
contentPadding = ComponentPadding(
horizontal = LocalSizes.current.spacingPrimary,
vertical = LocalSizes.current.spacingSecondary
),
contentShape = withAreaBoxShape(),
indicatorWidth = Dp.Unspecified,
indicatorHeight = DefaultTabIndicatorHeight,
indicatorShape = LocalShapes.current.tertiary
)
private val DefaultTabIndicatorHeight = 3.dp
private const val TabIndicatorDuration = 250 private const val TabIndicatorDuration = 250

View File

@@ -27,7 +27,7 @@ import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -36,8 +36,9 @@ import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.DefaultTypography import com.highcapable.flexiui.DefaultTypography
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.toColor
/** /**
* Flexi UI basic text. * Flexi UI basic text.
@@ -110,7 +111,7 @@ fun Text(
onTextLayout: (TextLayoutResult) -> Unit = {} onTextLayout: (TextLayoutResult) -> Unit = {}
) { ) {
val currentStyle = style ?: LocalTextStyle.current val currentStyle = style ?: LocalTextStyle.current
val currentColor = color.orNull() ?: currentStyle.color.orNull() ?: defaultTextColor() val currentColor = color.orNull() ?: currentStyle.color.orNull() ?: TextProperties.Color.toColor()
BasicText( BasicText(
text = text, text = text,
modifier = modifier, modifier = modifier,
@@ -124,9 +125,14 @@ fun Text(
) )
} }
@Stable
internal object TextProperties {
val Color = ColorsDescriptor.TextPrimary
}
/** /**
* CompositionLocal containing the preferred [TextStyle] * Composition local containing the preferred [TextStyle]
* that will be used by text by default. * that will be used by [Text] by default.
*/ */
val LocalTextStyle = compositionLocalOf { DefaultTextStyle } val LocalTextStyle = compositionLocalOf { DefaultTextStyle }
@@ -144,8 +150,4 @@ fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content) CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
} }
@Composable
@ReadOnlyComposable
internal fun defaultTextColor() = LocalColors.current.textPrimary
private val DefaultTextStyle = DefaultTypography.primary private val DefaultTextStyle = DefaultTypography.primary

View File

@@ -26,7 +26,6 @@ package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.hoverable import androidx.compose.foundation.hoverable
@@ -50,7 +49,6 @@ import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -89,26 +87,19 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.borderOrElse import com.highcapable.betterandroid.compose.extension.ui.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.componentState import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.betterandroid.compose.extension.ui.solidColor import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.resources.FlexiIcons import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.Backspace import com.highcapable.flexiui.resources.icon.Backspace
import com.highcapable.flexiui.resources.icon.ViewerClose import com.highcapable.flexiui.resources.icon.ViewerClose
import com.highcapable.flexiui.resources.icon.ViewerOpen import com.highcapable.flexiui.resources.icon.ViewerOpen
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
/** /**
* Colors defines for text field. * Colors defines for text field.
* @param textColor the text color. * @see TextFieldDefaults.colors
* @param cursorColor the cursor color.
* @param selectionColors the selection colors.
* @param completionColors the completion colors.
* @param placeholderContentColor the placeholder content color, usually for text color.
* @param decorInactiveTint the decoration inactive tint.
* @param decorActiveTint the decoration active tint.
* @param borderInactiveColor the border inactive color.
* @param borderActiveColor the border active color.
* @param backgroundColor the background color.
*/ */
@Immutable @Immutable
data class TextFieldColors( data class TextFieldColors(
@@ -124,8 +115,23 @@ data class TextFieldColors(
val backgroundColor: Color val backgroundColor: Color
) )
/**
* Style defines for text field.
* @see TextFieldDefaults.style
*/
@Immutable
data class TextFieldStyle(
val textStyle: TextStyle,
val completionStyle: DropdownMenuStyle,
val padding: ComponentPadding,
val shape: Shape,
val borderInactiveWith: Dp,
val borderActiveWith: Dp
)
/** /**
* Colors defines for auto complete box. * Colors defines for auto complete box.
* @see TextFieldDefaults.colors
* @param highlightContentColor the highlight content color, usually for text color. * @param highlightContentColor the highlight content color, usually for text color.
* @param menuColors the dropdown menu colors. * @param menuColors the dropdown menu colors.
*/ */
@@ -135,25 +141,6 @@ data class AutoCompleteBoxColors(
val menuColors: DropdownMenuColors val menuColors: DropdownMenuColors
) )
/**
* Style defines for text field.
* @param textStyle the text style.
* @param padding the padding of content.
* @param shape the shape.
* @param borderInactive the inactive border stroke.
* @param borderActive the active border stroke.
* @param completionStyle the completion dropdown menu style.
*/
@Immutable
data class TextFieldStyle(
val textStyle: TextStyle,
val padding: ComponentPadding,
val shape: Shape,
val borderInactive: BorderStroke,
val borderActive: BorderStroke,
val completionStyle: DropdownMenuStyle
)
/** /**
* Options defines for auto complete. * Options defines for auto complete.
* @param checkCase whether to check case, default is true. * @param checkCase whether to check case, default is true.
@@ -202,8 +189,8 @@ fun TextField(
onValueChange: (TextFieldValue) -> Unit, onValueChange: (TextFieldValue) -> Unit,
completionValues: List<String> = emptyList(), completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(), completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -228,24 +215,21 @@ fun TextField(
else -> colors.decorInactiveTint else -> colors.decorInactiveTint
}) })
val animatedBorderColor by animateColorAsState(when { val animatedBorderColor by animateColorAsState(when {
focused || hovered -> style.borderActive.solidColor focused || hovered -> colors.borderActiveColor
else -> style.borderInactive.solidColor else -> colors.borderInactiveColor
}) })
val animatedBorderWidth by animateDpAsState(when { val animatedBorderWidth by animateDpAsState(when {
focused -> style.borderActive.width focused -> style.borderActiveWith
else -> style.borderInactive.width else -> style.borderInactiveWith
}) })
val border = when {
focused || hovered -> style.borderInactive
else -> style.borderInactive
}.copy(animatedBorderWidth, SolidColor(animatedBorderColor))
val textColor = style.textStyle.color.orNull() ?: colors.textColor val textColor = style.textStyle.color.orNull() ?: colors.textColor
BoxWithConstraints( BoxWithConstraints(
modifier = Modifier.textField( modifier = Modifier.textField(
enabled = enabled, enabled = enabled,
colors = colors, colors = colors,
style = style, style = style,
border = border, borderColor = animatedBorderColor,
borderWidth = animatedBorderWidth,
interactionSource = interactionSource, interactionSource = interactionSource,
then = modifier then = modifier
).pointerHoverState(TextFieldPointerState.Text) ).pointerHoverState(TextFieldPointerState.Text)
@@ -351,8 +335,8 @@ fun TextField(
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
completionValues: List<String> = emptyList(), completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(), completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -427,8 +411,8 @@ fun PasswordTextField(
onValueChange: (TextFieldValue) -> Unit, onValueChange: (TextFieldValue) -> Unit,
defaultPasswordVisible: Boolean = false, defaultPasswordVisible: Boolean = false,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@@ -475,7 +459,7 @@ fun PasswordTextField(
if (value.text.isEmpty() && animatedSize == 0.dp) passwordVisible = defaultPasswordVisible if (value.text.isEmpty() && animatedSize == 0.dp) passwordVisible = defaultPasswordVisible
IconToggleButton( IconToggleButton(
modifier = Modifier.size(animatedSize).pointerHoverState(TextFieldPointerState.Common), modifier = Modifier.size(animatedSize).pointerHoverState(TextFieldPointerState.Common),
style = IconButtonDefaults.style.copy(padding = TextDecorIconPadding), style = IconButtonDefaults.style(padding = TextDecorIconPadding),
checked = passwordVisible, checked = passwordVisible,
onCheckedChange = { onCheckedChange = {
passwordVisible = it passwordVisible = it
@@ -518,8 +502,8 @@ fun PasswordTextField(
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
defaultPasswordVisible: Boolean = false, defaultPasswordVisible: Boolean = false,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@@ -589,8 +573,8 @@ fun BackspaceTextField(
onValueChange: (TextFieldValue) -> Unit, onValueChange: (TextFieldValue) -> Unit,
completionValues: List<String> = emptyList(), completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(), completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -647,7 +631,7 @@ fun BackspaceTextField(
focusRequester.requestFocus() focusRequester.requestFocus()
}, },
modifier = Modifier.width(animatedSize).pointerHoverState(TextFieldPointerState.Common), modifier = Modifier.width(animatedSize).pointerHoverState(TextFieldPointerState.Common),
style = IconButtonDefaults.style.copy(padding = TextDecorIconPadding), style = IconButtonDefaults.style(padding = TextDecorIconPadding),
enabled = enabled, enabled = enabled,
interactionSource = cInteractionSource interactionSource = cInteractionSource
) { Icon(imageVector = FlexiIcons.Backspace) } ) { Icon(imageVector = FlexiIcons.Backspace) }
@@ -688,8 +672,8 @@ fun BackspaceTextField(
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
completionValues: List<String> = emptyList(), completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors, colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style, style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true, enabled: Boolean = true,
readOnly: Boolean = false, readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(), completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -883,9 +867,10 @@ private fun TextFieldDecorationBox(
@Composable @Composable
private fun TextFieldStyle(colors: TextFieldColors, content: @Composable () -> Unit) { private fun TextFieldStyle(colors: TextFieldColors, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalTextSelectionColors provides colors.selectionColors) { CompositionLocalProvider(
content() LocalTextSelectionColors provides colors.selectionColors,
} content = content
)
} }
internal expect fun Modifier.pointerHoverState(state: TextFieldPointerState): Modifier internal expect fun Modifier.pointerHoverState(state: TextFieldPointerState): Modifier
@@ -902,7 +887,8 @@ private fun Modifier.textField(
enabled: Boolean, enabled: Boolean,
colors: TextFieldColors, colors: TextFieldColors,
style: TextFieldStyle, style: TextFieldStyle,
border: BorderStroke, borderColor: Color,
borderWidth: Dp,
interactionSource: MutableInteractionSource, interactionSource: MutableInteractionSource,
then: Modifier then: Modifier
) = composed( ) = composed(
@@ -911,7 +897,8 @@ private fun Modifier.textField(
properties["enabled"] = enabled properties["enabled"] = enabled
properties["colors"] = colors properties["colors"] = colors
properties["style"] = style properties["style"] = style
properties["border"] = border properties["borderColor"] = borderColor
properties["borderWidth"] = borderWidth
} }
) { ) {
componentState(enabled) componentState(enabled)
@@ -919,7 +906,7 @@ private fun Modifier.textField(
.hoverable(interactionSource, enabled) .hoverable(interactionSource, enabled)
.clip(style.shape) .clip(style.shape)
.background(colors.backgroundColor, style.shape) .background(colors.backgroundColor, style.shape)
.borderOrElse(border, style.shape) .borderOrElse(borderWidth, borderColor, style.shape)
.then(then) .then(then)
} }
@@ -940,55 +927,97 @@ private fun Modifier.textFieldPadding(
* Defaults of text field. * Defaults of text field.
*/ */
object TextFieldDefaults { object TextFieldDefaults {
val colors: TextFieldColors
/**
* Creates a [TextFieldColors] with the default values.
* @param textColor the text color.
* @param cursorColor the cursor color.
* @param selectionColors the selection colors.
* @param completionColors the completion colors.
* @param placeholderContentColor the placeholder content color, usually for text color.
* @param decorInactiveTint the decoration inactive tint.
* @param decorActiveTint the decoration active tint.
* @param borderInactiveColor the border inactive color.
* @param borderActiveColor the border active color.
* @param backgroundColor the background color.
* @return [TextFieldColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultTextFieldColors() textColor: Color = TextFieldProperties.TextColor.toColor(),
val style: TextFieldStyle cursorColor: Color = TextFieldProperties.CursorColor.toColor(),
selectionColors: TextSelectionColors = TextSelectionColors(
handleColor = TextFieldProperties.SelectionHandleColor.toColor(),
backgroundColor = TextFieldProperties.SelectionBackgroundColor.toColor()
),
completionColors: AutoCompleteBoxColors = AutoCompleteBoxColors(
highlightContentColor = TextFieldProperties.CompletionHighlightContentColor.toColor(),
menuColors = DropdownMenuDefaults.colors()
),
placeholderContentColor: Color = TextFieldProperties.PlaceholderContentColor.toColor(),
decorInactiveTint: Color = TextFieldProperties.DecorInactiveTint.toColor(),
decorActiveTint: Color = TextFieldProperties.DecorActiveTint.toColor(),
borderInactiveColor: Color = TextFieldProperties.BorderInactiveColor.toColor(),
borderActiveColor: Color = TextFieldProperties.BorderActiveColor.toColor(),
backgroundColor: Color = TextFieldProperties.BackgroundColor
) = TextFieldColors(
textColor = textColor,
cursorColor = cursorColor,
selectionColors = selectionColors,
completionColors = completionColors,
placeholderContentColor = placeholderContentColor,
decorInactiveTint = decorInactiveTint,
decorActiveTint = decorActiveTint,
borderInactiveColor = borderInactiveColor,
borderActiveColor = borderActiveColor,
backgroundColor = backgroundColor
)
/**
* Creates a [TextFieldStyle] with the default values.
* @param textStyle the text style.
* @param completionStyle the completion style.
* @param padding the padding of content.
* @param shape the shape.
* @param borderInactiveWith the inactive border width.
* @param borderActiveWith the active border width.
* @return [TextFieldStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultTextFieldStyle() textStyle: TextStyle = LocalTextStyle.current,
completionStyle: DropdownMenuStyle = DropdownMenuDefaults.style(),
padding: ComponentPadding = TextFieldProperties.Padding.toPadding(),
shape: Shape = AreaBoxDefaults.childShape(),
borderInactiveWith: Dp = TextFieldProperties.BorderInactiveWith.toDp(),
borderActiveWith: Dp = TextFieldProperties.BorderActiveWith.toDp()
) = TextFieldStyle(
textStyle = textStyle,
completionStyle = completionStyle,
padding = padding,
shape = shape,
borderInactiveWith = borderInactiveWith,
borderActiveWith = borderActiveWith
)
} }
@Composable @Stable
@ReadOnlyComposable internal object TextFieldProperties {
private fun defaultTextFieldColors() = TextFieldColors( val TextColor = TextProperties.Color
textColor = defaultTextColor(), val CursorColor = ColorsDescriptor.ThemePrimary
cursorColor = LocalColors.current.themePrimary, val SelectionHandleColor = ColorsDescriptor.ThemePrimary
selectionColors = TextSelectionColors( val SelectionBackgroundColor = ColorsDescriptor.ThemeSecondary
handleColor = LocalColors.current.themePrimary, val CompletionHighlightContentColor = ColorsDescriptor.ThemePrimary
backgroundColor = LocalColors.current.themeSecondary val PlaceholderContentColor = ColorsDescriptor.TextSecondary
), val DecorInactiveTint = ColorsDescriptor.ThemeSecondary
completionColors = AutoCompleteBoxColors( val DecorActiveTint = ColorsDescriptor.ThemePrimary
highlightContentColor = LocalColors.current.themePrimary, val BorderInactiveColor = ColorsDescriptor.ThemeSecondary
menuColors = DropdownMenuDefaults.colors val BorderActiveColor = ColorsDescriptor.ThemePrimary
), val BackgroundColor = Color.Transparent
placeholderContentColor = LocalColors.current.textSecondary, val Padding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
decorInactiveTint = LocalColors.current.themeSecondary, val BorderInactiveWith = SizesDescriptor.BorderSizeSecondary
decorActiveTint = LocalColors.current.themePrimary, val BorderActiveWith = SizesDescriptor.BorderSizePrimary
borderInactiveColor = LocalColors.current.themeSecondary, }
borderActiveColor = LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultTextFieldStyle() = TextFieldStyle(
textStyle = LocalTextStyle.current,
padding = ComponentPadding(LocalSizes.current.spacingSecondary),
shape = withAreaBoxShape(),
borderInactive = defaultTextFieldInactiveBorder(),
borderActive = defaultTextFieldActiveBorder(),
completionStyle = DropdownMenuDefaults.style
)
@Composable
@ReadOnlyComposable
private fun defaultTextFieldInactiveBorder() = BorderStroke(LocalSizes.current.borderSizeSecondary, LocalColors.current.themeSecondary)
@Composable
@ReadOnlyComposable
private fun defaultTextFieldActiveBorder() = BorderStroke(LocalSizes.current.borderSizePrimary, LocalColors.current.themePrimary)
private val TextDecorIconSize = 24.dp private val TextDecorIconSize = 24.dp
private val TextDecorIconPadding = ComponentPadding(2.dp) private val TextDecorIconPadding = ComponentPadding(2.dp)

View File

@@ -19,7 +19,7 @@
* *
* This file is created by fankes on 2023/11/10. * This file is created by fankes on 2023/11/10.
*/ */
@file:Suppress("unused") @file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component.interaction package com.highcapable.flexiui.component.interaction
@@ -27,7 +27,7 @@ import androidx.compose.foundation.Indication
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
@@ -39,14 +39,13 @@ import com.highcapable.betterandroid.compose.extension.ui.clickable
import com.highcapable.betterandroid.compose.extension.ui.combinedClickable import com.highcapable.betterandroid.compose.extension.ui.combinedClickable
import com.highcapable.betterandroid.compose.extension.ui.selectable import com.highcapable.betterandroid.compose.extension.ui.selectable
import com.highcapable.betterandroid.compose.extension.ui.toggleable import com.highcapable.betterandroid.compose.extension.ui.toggleable
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.toColor
import androidx.compose.material.ripple.rememberRipple as materialRememberRipple import androidx.compose.material.ripple.rememberRipple as materialRememberRipple
/** /**
* Style defines for ripple. * Style defines for ripple.
* @param bounded whether the ripple is bounded. * @see InteractionDefaults.rippleStyle
* @param radius the radius.
* @param color the color.
*/ */
@Immutable @Immutable
data class RippleStyle( data class RippleStyle(
@@ -62,7 +61,7 @@ data class RippleStyle(
* @return [Indication] * @return [Indication]
*/ */
@Composable @Composable
fun rememberRipple(style: RippleStyle = InteractionDefaults.rippleStyle) = fun rememberRipple(style: RippleStyle = InteractionDefaults.rippleStyle()) =
materialRememberRipple(style.bounded, style.radius, style.color) materialRememberRipple(style.bounded, style.radius, style.color)
/** /**
@@ -95,7 +94,7 @@ fun Modifier.rippleClickable(
properties["onClick"] = onClick properties["onClick"] = onClick
} }
) { ) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle) val currentIndication = rememberRipple(currentRippleStyle)
clickable( clickable(
onClick = onClick, onClick = onClick,
@@ -146,7 +145,7 @@ fun Modifier.rippleCombinedClickable(
properties["onClick"] = onClick properties["onClick"] = onClick
} }
) { ) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle) val currentIndication = rememberRipple(currentRippleStyle)
combinedClickable( combinedClickable(
onClick = onClick, onClick = onClick,
@@ -190,7 +189,7 @@ fun Modifier.rippleToggleable(
properties["onValueChange"] = onValueChange properties["onValueChange"] = onValueChange
} }
) { ) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle) val currentIndication = rememberRipple(currentRippleStyle)
toggleable( toggleable(
value = value, value = value,
@@ -231,7 +230,7 @@ fun Modifier.rippleSelectable(
properties["onClick"] = onClick properties["onClick"] = onClick
} }
) { ) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle) val currentIndication = rememberRipple(currentRippleStyle)
selectable( selectable(
selected = selected, selected = selected,
@@ -247,22 +246,35 @@ fun Modifier.rippleSelectable(
* Defaults of interaction. * Defaults of interaction.
*/ */
object InteractionDefaults { object InteractionDefaults {
val rippleStyle: RippleStyle
/**
* Creates a [RippleStyle] with the default values.
* @param bounded whether the ripple is bounded.
* @param radius the radius.
* @param color the color.
* @return [RippleStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun rippleStyle(
get() = LocalRippleStyle.current ?: defaultRippleStyle() bounded: Boolean = InteractionProperties.RippleBounded,
radius: Dp = InteractionProperties.RippleRadius,
color: Color = InteractionProperties.RippleColor.toColor()
) = RippleStyle(
bounded = bounded,
radius = radius,
color = color
)
}
@Stable
internal object InteractionProperties {
const val RippleBounded = true
val RippleRadius = Dp.Unspecified
val RippleColor = ColorsDescriptor.ThemeSecondary
} }
/** /**
* CompositionLocal containing the preferred [RippleStyle] * Composition local containing the preferred [RippleStyle]
* that will be used by interaction by default. * that will be used by interaction by default.
*/ */
val LocalRippleStyle = compositionLocalOf<RippleStyle?> { null } val LocalRippleStyle = compositionLocalOf<RippleStyle?> { null }
@Composable
@ReadOnlyComposable
private fun defaultRippleStyle() = RippleStyle(
bounded = true,
radius = Dp.Unspecified,
color = LocalColors.current.themeSecondary
)

View File

@@ -41,12 +41,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@@ -55,11 +56,14 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.layout.AdaptiveRow import com.highcapable.betterandroid.compose.extension.ui.layout.AdaptiveRow
import com.highcapable.betterandroid.compose.extension.ui.window.Dialog import com.highcapable.betterandroid.compose.extension.ui.window.Dialog
import com.highcapable.betterandroid.compose.extension.ui.window.DialogPropertiesWrapper import com.highcapable.betterandroid.compose.extension.ui.window.DialogPropertiesWrapper
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalTypography import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.component.AreaBox import com.highcapable.flexiui.component.AreaBox
import com.highcapable.flexiui.component.AreaBoxColors
import com.highcapable.flexiui.component.AreaBoxDefaults import com.highcapable.flexiui.component.AreaBoxDefaults
import com.highcapable.flexiui.component.AreaBoxProperties
import com.highcapable.flexiui.component.AreaBoxStyle import com.highcapable.flexiui.component.AreaBoxStyle
import com.highcapable.flexiui.component.Button import com.highcapable.flexiui.component.Button
import com.highcapable.flexiui.component.Icon import com.highcapable.flexiui.component.Icon
@@ -69,15 +73,19 @@ import com.highcapable.flexiui.component.LocalTextStyle
import com.highcapable.flexiui.component.Text import com.highcapable.flexiui.component.Text
import com.highcapable.flexiui.platform.ActualPlatform import com.highcapable.flexiui.platform.ActualPlatform
import com.highcapable.flexiui.platform.Platform import com.highcapable.flexiui.platform.Platform
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import com.highcapable.flexiui.toTextStyle
/** /**
* Colors defines for flexi dialog. * Colors defines for flexi dialog.
* @param titleTextColor the title text color. * @see FlexiDialogDefaults.colors
* @param titleIconTint the title icon tint.
* @param contentTextColor the content text color.
*/ */
@Immutable @Immutable
data class FlexiDialogColors( data class FlexiDialogColors(
val backgroundColor: Color,
val borderColor: Color,
val titleTextColor: Color, val titleTextColor: Color,
val titleIconTint: Color, val titleIconTint: Color,
val contentTextColor: Color val contentTextColor: Color
@@ -85,26 +93,21 @@ data class FlexiDialogColors(
/** /**
* Style defines for flexi dialog. * Style defines for flexi dialog.
* @param boxStyle the style of area box. * @see FlexiDialogDefaults.style
* @param titleTextStyle the title text style.
* @param contentTextStyle the content text style.
* @param maxWidth the dialog's max width.
* @param maxHeight the dialog's max height.
* @param outPadding the dialog's out padding.
* @param titlePadding the title padding.
* @param contentPadding the content padding.
* @param buttonsSpacing the spacing between buttons.
*/ */
@Immutable @Immutable
data class FlexiDialogStyle( data class FlexiDialogStyle(
val boxStyle: AreaBoxStyle,
val titleTextStyle: TextStyle, val titleTextStyle: TextStyle,
val contentTextStyle: TextStyle, val contentTextStyle: TextStyle,
val maxWidth: Dp, val maxWidth: Dp,
val maxHeight: Dp, val maxHeight: Dp,
val outPadding: ComponentPadding, val padding: ComponentPadding,
val insetsPadding: ComponentPadding,
val titlePadding: ComponentPadding, val titlePadding: ComponentPadding,
val contentPadding: ComponentPadding, val contentPadding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val shadowSize: Dp,
val buttonsSpacing: Dp val buttonsSpacing: Dp
) )
@@ -130,8 +133,8 @@ fun FlexiDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
animated: Boolean = true, animated: Boolean = true,
colors: FlexiDialogColors = FlexiDialogDefaults.colors, colors: FlexiDialogColors = FlexiDialogDefaults.colors(),
style: FlexiDialogStyle = FlexiDialogDefaults.style, style: FlexiDialogStyle = FlexiDialogDefaults.style(),
contentAlignment: Alignment = Alignment.TopStart, contentAlignment: Alignment = Alignment.TopStart,
properties: DialogPropertiesWrapper = DefaultDialogProperties, properties: DialogPropertiesWrapper = DefaultDialogProperties,
title: (@Composable RowScope.() -> Unit)? = null, title: (@Composable RowScope.() -> Unit)? = null,
@@ -185,8 +188,8 @@ fun FlexiDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
animated: Boolean = true, animated: Boolean = true,
scrimAnimated: Boolean = false, scrimAnimated: Boolean = false,
colors: FlexiDialogColors = FlexiDialogDefaults.colors, colors: FlexiDialogColors = FlexiDialogDefaults.colors(),
style: FlexiDialogStyle = FlexiDialogDefaults.style, style: FlexiDialogStyle = FlexiDialogDefaults.style(),
contentAlignment: Alignment = Alignment.TopStart, contentAlignment: Alignment = Alignment.TopStart,
properties: DialogPropertiesWrapper = DefaultDialogProperties, properties: DialogPropertiesWrapper = DefaultDialogProperties,
title: (@Composable RowScope.() -> Unit)? = null, title: (@Composable RowScope.() -> Unit)? = null,
@@ -248,10 +251,20 @@ fun FlexiDialog(
BasicFlexiDialog( BasicFlexiDialog(
visible = visible, visible = visible,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
modifier = Modifier.padding(style.outPadding).then(modifier), modifier = modifier,
animated = animated, animated = animated,
scrimAnimated = scrimAnimated, scrimAnimated = scrimAnimated,
boxStyle = style.boxStyle, boxColors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
boxStyle = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
insetsPadding = style.insetsPadding,
maxWidth = style.maxWidth, maxWidth = style.maxWidth,
maxHeight = style.maxHeight, maxHeight = style.maxHeight,
contentAlignment = contentAlignment, contentAlignment = contentAlignment,
@@ -276,7 +289,9 @@ private fun BasicFlexiDialog(
modifier: Modifier, modifier: Modifier,
animated: Boolean, animated: Boolean,
scrimAnimated: Boolean, scrimAnimated: Boolean,
boxColors: AreaBoxColors,
boxStyle: AreaBoxStyle, boxStyle: AreaBoxStyle,
insetsPadding: ComponentPadding,
maxWidth: Dp, maxWidth: Dp,
maxHeight: Dp, maxHeight: Dp,
contentAlignment: Alignment, contentAlignment: Alignment,
@@ -305,17 +320,17 @@ private fun BasicFlexiDialog(
properties = propertiesCopy, properties = propertiesCopy,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Box( val sModifier = if (animated)
modifier = if (animated)
Modifier.graphicsLayer { Modifier.graphicsLayer {
scaleX = animatedScale scaleX = animatedScale
scaleY = animatedScale scaleY = animatedScale
alpha = animatedAlpha alpha = animatedAlpha
}.then(modifier) }.then(modifier)
else Modifier.then(modifier) else modifier
) { Box(modifier = Modifier.padding(insetsPadding).then(sModifier)) {
AreaBox( AreaBox(
modifier = Modifier.widthIn(max = maxWidth).heightIn(max = maxHeight), modifier = Modifier.widthIn(max = maxWidth).heightIn(max = maxHeight),
colors = boxColors,
style = boxStyle, style = boxStyle,
contentAlignment = contentAlignment contentAlignment = contentAlignment
) { content() } ) { content() }
@@ -327,45 +342,101 @@ private fun BasicFlexiDialog(
* Defaults of flexi dialog. * Defaults of flexi dialog.
*/ */
object FlexiDialogDefaults { object FlexiDialogDefaults {
val colors: FlexiDialogColors
/**
* Creates a [FlexiDialogColors] with the default values.
* @param backgroundColor the background color.
* @param borderColor the border color.
* @param titleTextColor the title text color.
* @param titleIconTint the title icon tint.
* @param contentTextColor the content text color.
* @return [FlexiDialogColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = defaultFlexiDialogColors() backgroundColor: Color = FlexiDialogProperties.BackgroundColor.toColor(),
val style: FlexiDialogStyle borderColor: Color = FlexiDialogProperties.BorderColor.toColor(),
titleTextColor: Color = FlexiDialogProperties.TitleTextColor.toColor(),
titleIconTint: Color = FlexiDialogProperties.TitleIconTint.toColor(),
contentTextColor: Color = FlexiDialogProperties.ContentTextColor.toColor()
) = FlexiDialogColors(
backgroundColor = backgroundColor,
borderColor = borderColor,
titleTextColor = titleTextColor,
titleIconTint = titleIconTint,
contentTextColor = contentTextColor
)
/**
* Creates a [FlexiDialogStyle] with the default values.
* @param titleTextStyle the title text style.
* @param contentTextStyle the content text style.
* @param maxWidth the dialog's max width.
* @param maxHeight the dialog's max height.
* @param padding the dialog's padding.
* @param insetsPadding the dialog's insets padding.
* @param titlePadding the title padding.
* @param contentPadding the content padding.
* @param shape the dialog's shape.
* @param borderWidth the dialog's border width.
* @param shadowSize the dialog's shadow size.
* @param buttonsSpacing the spacing between buttons.
* @return [FlexiDialogStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = defaultFlexiDialogStyle() titleTextStyle: TextStyle = FlexiDialogProperties.TitleTextStyle.toTextStyle(),
contentTextStyle: TextStyle = FlexiDialogProperties.ContentTextStyle.toTextStyle(),
maxWidth: Dp = FlexiDialogProperties.MaxWidth,
maxHeight: Dp = FlexiDialogProperties.MaxHeight,
padding: ComponentPadding = FlexiDialogProperties.Padding.toPadding(),
insetsPadding: ComponentPadding = FlexiDialogProperties.InsetsPadding,
titlePadding: ComponentPadding = FlexiDialogProperties.TitlePadding.toPadding(),
contentPadding: ComponentPadding = FlexiDialogProperties.ContentPadding.toPadding(),
shape: Shape = FlexiDialogProperties.Shape.toShape(),
borderWidth: Dp = FlexiDialogProperties.BorderWidth.toDp(),
shadowSize: Dp = FlexiDialogProperties.ShadowSize,
buttonsSpacing: Dp = FlexiDialogProperties.ButtonsSpacing.toDp()
) = FlexiDialogStyle(
titleTextStyle = titleTextStyle,
contentTextStyle = contentTextStyle,
maxWidth = maxWidth,
maxHeight = maxHeight,
padding = padding,
insetsPadding = insetsPadding,
titlePadding = titlePadding,
contentPadding = contentPadding,
shape = shape,
borderWidth = borderWidth,
shadowSize = shadowSize,
buttonsSpacing = buttonsSpacing
)
} }
@Composable @Stable
@ReadOnlyComposable internal object FlexiDialogProperties {
private fun defaultFlexiDialogColors() = FlexiDialogColors( val BackgroundColor = AreaBoxProperties.BackgroundColor
titleTextColor = LocalColors.current.textPrimary, val BorderColor = AreaBoxProperties.BorderColor
titleIconTint = LocalColors.current.textPrimary, val TitleTextColor = ColorsDescriptor.TextPrimary
contentTextColor = LocalColors.current.textSecondary val TitleIconTint = ColorsDescriptor.TextPrimary
) val ContentTextColor = ColorsDescriptor.TextSecondary
val TitleTextStyle = TypographyDescriptor.TitleSecondary
@Composable val ContentTextStyle = TypographyDescriptor.Primary
@ReadOnlyComposable val MaxWidth = 300.dp
private fun defaultFlexiDialogStyle() = FlexiDialogStyle( val MaxHeight = 500.dp
boxStyle = AreaBoxDefaults.style.copy(padding = ComponentPadding(LocalSizes.current.spacingSecondary)), val Padding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
titleTextStyle = LocalTypography.current.titleSecondary, val InsetsPadding = ComponentPadding(horizontal = 50.dp)
contentTextStyle = LocalTypography.current.primary, val TitlePadding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
maxWidth = DefaultMaxWidth, val ContentPadding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
maxHeight = DefaultMaxHeight, val Shape = AreaBoxProperties.Shape
outPadding = ComponentPadding(horizontal = DefaultHorizontalOutPadding), val BorderWidth = AreaBoxProperties.BorderWidth
titlePadding = ComponentPadding(LocalSizes.current.spacingSecondary), val ShadowSize = AreaBoxProperties.ShadowSize
contentPadding = ComponentPadding(LocalSizes.current.spacingSecondary), val ButtonsSpacing = SizesDescriptor.SpacingSecondary
buttonsSpacing = LocalSizes.current.spacingSecondary }
)
private const val AnimationDuration = 250 private const val AnimationDuration = 250
private const val AnimationMinmumScale = 0.8f private const val AnimationMinmumScale = 0.8f
private val DefaultMaxWidth = 300.dp
private val DefaultMaxHeight = 500.dp
private val DefaultHorizontalOutPadding = 50.dp
private const val DefaultScrimOpacity = 0.35f private const val DefaultScrimOpacity = 0.35f
private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity) private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity)
private val DefaultDialogProperties = DialogPropertiesWrapper(usePlatformDefaultWidth = false, scrimColor = DefaultScrimColor) private val DefaultDialogProperties = DialogPropertiesWrapper(usePlatformDefaultWidth = false, scrimColor = DefaultScrimColor)

View File

@@ -26,25 +26,19 @@ package com.highcapable.flexiui
import androidx.compose.foundation.LocalContextMenuRepresentation import androidx.compose.foundation.LocalContextMenuRepresentation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import com.highcapable.flexiui.component.DesktopContextMenuDefaults import com.highcapable.flexiui.component.ContextMenuDefaults
import com.highcapable.flexiui.component.DesktopContextMenuRepresentation import com.highcapable.flexiui.component.FlexiContextMenuRepresentation
import com.highcapable.flexiui.component.LocalContextMenuColors import com.highcapable.flexiui.component.LocalContextMenuColors
import com.highcapable.flexiui.component.LocalContextMenuStyle import com.highcapable.flexiui.component.LocalContextMenuStyle
import com.highcapable.flexiui.component.defaultContextMenuColors
import com.highcapable.flexiui.component.defaultContextMenuStyle
@Composable @Composable
internal actual fun FlexiThemeContent(content: @Composable () -> Unit) { internal actual fun FlexiThemeContent(content: @Composable () -> Unit) {
CompositionLocalProvider( CompositionLocalProvider(
LocalContextMenuColors provides defaultContextMenuColors(), LocalContextMenuColors provides ContextMenuDefaults.colors(),
LocalContextMenuStyle provides defaultContextMenuStyle() LocalContextMenuStyle provides ContextMenuDefaults.style(),
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
LocalContextMenuRepresentation provides LocalContextMenuRepresentation provides FlexiContextMenuRepresentation(),
DesktopContextMenuRepresentation(
colors = DesktopContextMenuDefaults.colors,
style = DesktopContextMenuDefaults.style
),
content = content content = content
) )
} }

View File

@@ -35,7 +35,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.InputMode import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
@@ -56,22 +57,25 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.orNull import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.LocalColors import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.LocalShapes import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.LocalSizes import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
/** /**
* Colors defines for the context menu. * Colors defines for the context menu.
* @param contentColor the content color, usually for the text color. * @see ContextMenuDefaults.colors
* @param borderColor the border color.
*/ */
@Immutable @Immutable
data class ContextMenuColors( data class ContextMenuColors(
@@ -81,26 +85,26 @@ data class ContextMenuColors(
/** /**
* Style defines for the context menu. * Style defines for the context menu.
* @param contentStyle the content style of area box. * @see ContextMenuDefaults.style
* @param borderStyle the border style of area box.
*/ */
@Immutable @Immutable
data class ContextMenuStyle( data class ContextMenuStyle(
val contentStyle: AreaBoxStyle?, val padding: ComponentPadding,
val borderStyle: AreaBoxStyle? val shape: Shape,
val borderWidth: Dp,
val contentPadding: ComponentPadding,
val contentShape: Shape,
val shadowSize: Dp
) )
internal class DesktopContextMenuRepresentation( internal class FlexiContextMenuRepresentation : ContextMenuRepresentation {
private val colors: ContextMenuColors,
private val style: ContextMenuStyle
) : ContextMenuRepresentation {
@Composable @Composable
override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) { override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) {
val colors = LocalContextMenuColors.current ?: ContextMenuDefaults.colors()
val style = LocalContextMenuStyle.current ?: ContextMenuDefaults.style()
val status = state.status val status = state.status
if (status is ContextMenuState.Status.Open) { if (status is ContextMenuState.Status.Open) {
val contentStyle = style.contentStyle ?: return
val borderStyle = style.borderStyle ?: return
var focusManager: FocusManager? by mutableStateOf(null) var focusManager: FocusManager? by mutableStateOf(null)
var inputModeManager: InputModeManager? by mutableStateOf(null) var inputModeManager: InputModeManager? by mutableStateOf(null)
Popup( Popup(
@@ -129,12 +133,20 @@ internal class DesktopContextMenuRepresentation(
modifier = Modifier modifier = Modifier
.width(IntrinsicSize.Max) .width(IntrinsicSize.Max)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
color = colors.borderColor, colors = AreaBoxDefaults.colors(backgroundColor = colors.borderColor),
style = borderStyle style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) { ) {
items().forEach { item -> items().forEach { item ->
MenuItemContent( MenuItemContent(
style = contentStyle, style = AreaBoxDefaults.style(
padding = style.contentPadding,
shape = style.contentShape
),
onClick = { onClick = {
state.status = ContextMenuState.Status.Closed state.status = ContextMenuState.Status.Closed
item.onClick() item.onClick()
@@ -164,7 +176,7 @@ private fun MenuItemContent(
maxWidth = MenuContentMaxWidth, maxWidth = MenuContentMaxWidth,
minHeight = MenuContentMinHeight minHeight = MenuContentMinHeight
), ),
color = Color.Transparent, colors = AreaBoxDefaults.colors(backgroundColor = Color.Transparent),
style = style, style = style,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { content() } ) { content() }
@@ -185,61 +197,74 @@ private fun Modifier.onHover(onHover: (Boolean) -> Unit) = pointerInput(Unit) {
/** /**
* Defaults of context menu. * Defaults of context menu.
*/ */
object DesktopContextMenuDefaults { object ContextMenuDefaults {
val colors: ContextMenuColors
/**
* Creates a [ContextMenuColors] with the default values.
* @param contentColor the content color, usually for the text color.
* @param borderColor the border color.
* @return [ContextMenuColors]
*/
@Composable @Composable
@ReadOnlyComposable fun colors(
get() = LocalContextMenuColors.current contentColor: Color = ContextMenuProperties.ContentColor.toColor(),
val style: ContextMenuStyle borderColor: Color = ContextMenuProperties.BorderColor.toColor()
) = ContextMenuColors(
contentColor = contentColor,
borderColor = borderColor
)
/**
* Creates a [ContextMenuStyle] with the default values.
* @param padding the menu padding.
* @param shape the menu shape.
* @param borderWidth the menu border width.
* @param contentPadding the content padding.
* @param contentShape the content shape.
* @param shadowSize the shadow size.
* @return [ContextMenuStyle]
*/
@Composable @Composable
@ReadOnlyComposable fun style(
get() = LocalContextMenuStyle.current padding: ComponentPadding = ContextMenuProperties.Padding.toPadding(),
shape: Shape = ContextMenuProperties.Shape.toShape(),
borderWidth: Dp = ContextMenuProperties.BorderWidth.toDp(),
contentPadding: ComponentPadding = ContextMenuProperties.ContentPadding,
contentShape: Shape = ContextMenuProperties.ContentShape.toShape(),
shadowSize: Dp = ContextMenuProperties.ShadowSize.toDp()
) = ContextMenuStyle(
padding = padding,
shape = shape,
borderWidth = borderWidth,
contentPadding = contentPadding,
contentShape = contentShape,
shadowSize = shadowSize
)
}
@Stable
internal object ContextMenuProperties {
val ContentColor = ColorsDescriptor.TextPrimary
val BorderColor = ColorsDescriptor.BackgroundSecondary
val Padding = PaddingDescriptor(SizesDescriptor.SpacingTertiary)
val Shape = ShapesDescriptor.Primary
val BorderWidth = AreaBoxProperties.BorderWidth
val ContentPadding = ComponentPadding(horizontal = 16.dp)
val ContentShape = ShapesDescriptor.Secondary
val ShadowSize = SizesDescriptor.ZoomSizeTertiary
} }
/** /**
* CompositionLocal containing the preferred [ContextMenuColors] * Composition local containing the preferred [ContextMenuColors]
* that will be used by context menu by default. * that will be used by [ContextMenuRepresentation] by default.
*/ */
val LocalContextMenuColors = compositionLocalOf { val LocalContextMenuColors = compositionLocalOf<ContextMenuColors?> { null }
ContextMenuColors(
borderColor = Color.Unspecified,
contentColor = Color.Unspecified
)
}
/** /**
* CompositionLocal containing the preferred [ContextMenuStyle] * Composition local containing the preferred [ContextMenuStyle]
* that will be used by context menu by default. * that will be used by [ContextMenuRepresentation] by default.
*/ */
val LocalContextMenuStyle = compositionLocalOf { val LocalContextMenuStyle = compositionLocalOf<ContextMenuStyle?> { null }
ContextMenuStyle(
contentStyle = null,
borderStyle = null
)
}
@Composable
@ReadOnlyComposable
internal fun defaultContextMenuColors() = ContextMenuColors(
contentColor = LocalContextMenuColors.current.contentColor.orNull() ?: LocalColors.current.textPrimary,
borderColor = LocalContextMenuColors.current.borderColor.orNull() ?: LocalColors.current.backgroundSecondary
)
@Composable
@ReadOnlyComposable
internal fun defaultContextMenuStyle() = ContextMenuStyle(
contentStyle = LocalContextMenuStyle.current.contentStyle ?: AreaBoxDefaults.style.copy(
padding = ComponentPadding(horizontal = DefaultMenuContentPadding),
shape = LocalShapes.current.secondary
),
borderStyle = LocalContextMenuStyle.current.borderStyle ?: AreaBoxDefaults.style.copy(
padding = ComponentPadding(LocalSizes.current.spacingTertiary),
shadowSize = LocalSizes.current.zoomSizeTertiary,
shape = LocalShapes.current.primary
)
)
private val DefaultMenuContentPadding = 16.dp
private val MenuContentMinWidth = 112.dp private val MenuContentMinWidth = 112.dp
private val MenuContentMaxWidth = 280.dp private val MenuContentMaxWidth = 280.dp