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
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
@@ -43,6 +45,22 @@ data class Colors(
var textSecondary: Color
)
/**
* Descriptor for [Colors].
*/
@Stable
internal enum class ColorsDescriptor {
BackgroundPrimary,
BackgroundSecondary,
ForegroundPrimary,
ForegroundSecondary,
ThemePrimary,
ThemeSecondary,
ThemeTertiary,
TextPrimary,
TextSecondary
}
internal val DefaultLightColors = Colors(
backgroundPrimary = Color(0xFFF5F5F5),
backgroundSecondary = Color(0xFFFAFAFA),
@@ -420,3 +438,30 @@ fun blueColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when
}
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.
*/
object FlexiTheme {
/**
* Retrieves the current [Colors] at the call site's position in the hierarchy.
* @return [Colors]
*/
val colors: Colors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
/**
* Retrieves the current [Shapes] at the call site's position in the hierarchy.
* @return [Shapes]
*/
val shapes: Shapes
@Composable
@ReadOnlyComposable
get() = LocalShapes.current
/**
* Retrieves the current [Typography] at the call site's position in the hierarchy.
* @return [Typography]
*/
val typography: Typography
@Composable
@ReadOnlyComposable
get() = LocalTypography.current
/**
* Retrieves the current [Sizes] at the call site's position in the hierarchy.
* @return [Sizes]
*/
val sizes: Sizes
@Composable
@ReadOnlyComposable

View File

@@ -26,8 +26,12 @@ package com.highcapable.flexiui
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp
/**
@@ -40,6 +44,16 @@ data class Shapes(
val tertiary: CornerBasedShape
)
/**
* Descriptor for [Shapes].
*/
@Stable
internal enum class ShapesDescriptor {
Primary,
Secondary,
Tertiary
}
internal val LocalShapes = staticCompositionLocalOf { DefaultShapes }
internal val DefaultShapes = Shapes(
@@ -47,3 +61,24 @@ internal val DefaultShapes = Shapes(
secondary = RoundedCornerShape(10.dp),
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
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf
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.
@@ -48,6 +52,85 @@ data class Sizes(
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 DefaultSizes = Sizes(
@@ -65,6 +148,36 @@ internal val DefaultSizes = Sizes(
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,
* this is the old design plan.

View File

@@ -23,7 +23,10 @@
package com.highcapable.flexiui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -42,6 +45,18 @@ data class Typography(
val secondary: TextStyle
)
/**
* Descriptor for [Typography].
*/
@Stable
internal enum class TypographyDescriptor {
TitlePrimary,
TitleSecondary,
Subtitle,
Primary,
Secondary
}
internal val LocalTypography = staticCompositionLocalOf { DefaultTypography }
private val DefaultLineHeight = 1.5.em
@@ -70,3 +85,26 @@ internal val DefaultTypography = Typography(
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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 com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.LocalTypography
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.ArrowNaviUp
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.
* @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.
* @see AppBarDefaults.colors
*/
@Immutable
data class AppBarColors(
@@ -71,13 +71,7 @@ data class AppBarColors(
/**
* Style defines for 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.
* @see AppBarDefaults.style
*/
@Immutable
data class AppBarStyle(
@@ -103,8 +97,8 @@ data class AppBarStyle(
@Composable
fun PrimaryAppBar(
modifier: Modifier = Modifier,
colors: AppBarColors? = null,
style: AppBarStyle? = null,
colors: AppBarColors = AppBarDefaults.colors(),
style: AppBarStyle = AppBarDefaults.style(type = AppBarType.Primary),
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null,
actions: @Composable (AppBarScope.() -> Unit)? = null
@@ -137,8 +131,8 @@ fun PrimaryAppBar(
@Composable
fun SecondaryAppBar(
modifier: Modifier = Modifier,
colors: AppBarColors? = null,
style: AppBarStyle? = null,
colors: AppBarColors = AppBarDefaults.colors(),
style: AppBarStyle = AppBarDefaults.style(type = AppBarType.Secondary),
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null,
finishIcon: @Composable (AppBarScope.() -> Unit)? = null,
@@ -165,29 +159,25 @@ fun SecondaryAppBar(
private fun BasicAppBar(
type: AppBarType,
modifier: Modifier,
colors: AppBarColors?,
style: AppBarStyle?,
colors: AppBarColors,
style: AppBarStyle,
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)?,
finishIcon: @Composable (AppBarScope.() -> Unit)?,
navigationIcon: @Composable (AppBarScope.() -> Unit)?,
actions: @Composable (AppBarScope.() -> Unit)?
) {
CompositionLocalProvider(LocalAppBarType provides type) {
val currentColors = colors ?: AppBarDefaults.colors
val currentStyle = style ?: AppBarDefaults.style
Box(modifier = modifier.padding(currentStyle.padding)) {
AppBarImpl(
type = type,
colors = currentColors,
style = currentStyle,
titleText = titleText,
subText = subText,
finishIcon = finishIcon,
navigationIcon = navigationIcon,
actions = actions
).Content()
}
Box(modifier = modifier.padding(style.padding)) {
AppBarImpl(
type = type,
colors = colors,
style = style,
titleText = titleText,
subText = subText,
finishIcon = finishIcon,
navigationIcon = navigationIcon,
actions = actions
).Content()
}
}
@@ -210,8 +200,8 @@ interface AppBarScope {
fun FinishIconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors,
style: ButtonStyle = IconButtonDefaults.style,
colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
@@ -238,8 +228,8 @@ interface AppBarScope {
fun NavigationIconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors,
style: ButtonStyle = IconButtonDefaults.style,
colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
@@ -267,8 +257,8 @@ interface AppBarScope {
fun ActionIconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors,
style: ButtonStyle = IconButtonDefaults.style,
colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
@@ -394,55 +384,99 @@ private class AppBarImpl(
@Stable
private val AppBarScope.impl get() = this as? AppBarImpl? ?: error("Could not got AppBarScope's impl.")
/**
* App bar's type definition.
*/
@Stable
private enum class AppBarType { Primary, Secondary }
enum class AppBarType {
/** @see PrimaryAppBar */
Primary,
/** @see SecondaryAppBar */
Secondary
}
/**
* Defaults of app bar.
*/
object AppBarDefaults {
val colors: AppBarColors
@Composable
@ReadOnlyComposable
get() = defaultAppBarColors()
val style: AppBarStyle
@Composable
@ReadOnlyComposable
get() = defaultAppBarStyle()
/**
* 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
fun colors(
titleTextColor: Color = AppBarProperties.TitleTextColor.toColor(),
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
fun style(
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 }
@Composable
@ReadOnlyComposable
private fun defaultAppBarColors() = AppBarColors(
titleTextColor = LocalColors.current.textPrimary,
subTextColor = LocalColors.current.textSecondary,
actionContentColor = LocalColors.current.textPrimary
)
@Composable
@ReadOnlyComposable
private fun defaultAppBarStyle() = AppBarStyle(
padding = when {
LocalInSurface.current || LocalInAreaBox.current ->
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
@Stable
internal object AppBarProperties {
val TitleTextColor = ColorsDescriptor.TextPrimary
val SubTextColor = ColorsDescriptor.TextSecondary
val ActionContentColor = ColorsDescriptor.TextPrimary
val Padding = PaddingDescriptor(SizesDescriptor.SpacingPrimary)
val InBoxPadding = PaddingDescriptor(vertical = SizesDescriptor.SpacingPrimary)
val ContentSpacing = SizesDescriptor.SpacingSecondary
val PrimaryTitleTextStyle = TypographyDescriptor.TitlePrimary
val SecondaryTitleTextStyle = TypographyDescriptor.TitleSecondary
val SubTextStyle = TypographyDescriptor.Subtitle
val PrimaryActionIconSize = SizesDescriptor.IconSizePrimary
val SecondaryActionIconSize = SizesDescriptor.IconSizeSecondary
val ActionIconPadding = SizesDescriptor.SpacingTertiary
val ActionContentMaxWidth = 170.dp
}
private const val VerticalContentSpacingRatio = 1.6f

View File

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

View File

@@ -19,11 +19,10 @@
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
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.platform.debugInspectorInfo
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.borderOrElse
import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.InteractionDefaults
import com.highcapable.flexiui.component.interaction.RippleStyle
import com.highcapable.flexiui.component.interaction.rippleClickable
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.
* @param contentColor the content color, usually for icon tint and text color.
* @param backgroundColor the background color.
* @see ButtonDefaults.colors
* @see IconButtonDefaults.colors
*/
@Immutable
data class ButtonColors(
val contentColor: Color,
val backgroundColor: Color
val backgroundColor: Color,
val borderColor: Color
)
/**
* Style defines for button.
* @param rippleStyle the ripple style of this button.
* @param padding the padding of content.
* @param shape the shape.
* @param border the border stroke.
* @see ButtonDefaults.style
* @see IconButtonDefaults.style
*/
@Immutable
data class ButtonStyle(
val rippleStyle: RippleStyle,
val padding: ComponentPadding,
val shape: Shape,
val border: BorderStroke
val borderWidth: Dp
)
/**
@@ -100,8 +103,8 @@ data class ButtonStyle(
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.colors,
style: ButtonStyle = ButtonDefaults.style,
colors: ButtonColors = ButtonDefaults.colors(),
style: ButtonStyle = ButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
header: @Composable () -> Unit = {},
@@ -109,7 +112,7 @@ fun Button(
content: @Composable RowScope.() -> Unit
) {
val localTextStyle = LocalTextStyle.current.copy(color = colors.contentColor)
val localProgressIndicatorColors = LocalProgressIndicatorColors.current.copy(
val localProgressIndicatorColors = LocalProgressIndicatorColors.current?.copy(
foregroundColor = colors.contentColor,
backgroundColor = Color.Transparent
)
@@ -161,8 +164,8 @@ fun Button(
fun IconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors,
style: ButtonStyle = IconButtonDefaults.style,
colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
@@ -181,12 +184,7 @@ fun IconButton(
onClick = onClick
).padding(style.padding),
contentAlignment = Alignment.Center,
) {
CompositionLocalProvider(
LocalIconStyle provides LocalIconStyle.current.copy(tint = colors.contentColor),
content = content
)
}
) { IconButtonStyle(colors, content) }
}
/**
@@ -207,8 +205,8 @@ fun IconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = IconButtonDefaults.colors,
style: ButtonStyle = IconButtonDefaults.style,
colors: ButtonColors = IconButtonDefaults.colors(),
style: ButtonStyle = IconButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
@@ -228,12 +226,21 @@ fun IconToggleButton(
interactionSource = interactionSource
).padding(style.padding),
contentAlignment = Alignment.Center
) {
CompositionLocalProvider(
LocalIconStyle provides LocalIconStyle.current.copy(tint = colors.contentColor),
content = content
)
}
) { 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(
LocalIconStyle provides iconStyle,
content = content
)
}
private fun Modifier.button(
@@ -252,7 +259,7 @@ private fun Modifier.button(
componentState(enabled)
.clip(style.shape)
.background(colors.backgroundColor, style.shape)
.borderOrElse(style.border, style.shape)
.borderOrElse(style.borderWidth, colors.borderColor, style.shape)
.then(then)
}
@@ -260,104 +267,127 @@ private fun Modifier.button(
* Defaults of button.
*/
object ButtonDefaults {
val colors: ButtonColors
@Composable
@ReadOnlyComposable
get() = defaultButtonColors()
val style: ButtonStyle
@Composable
@ReadOnlyComposable
get() = defaultButtonStyle()
/**
* 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
fun colors(
contentColor: Color = when {
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
fun style(
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.
*/
object IconButtonDefaults {
val colors: ButtonColors
@Composable
@ReadOnlyComposable
get() = defaultIconButtonColors()
val style: ButtonStyle
@Composable
@ReadOnlyComposable
get() = defaultIconButtonStyle()
/**
* 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
fun colors(
contentColor: Color = IconButtonProperties.ContentColor.toColor(),
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
fun style(
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 }
@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.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
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.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.clickable
import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.CheckMark
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
/**
* Colors defines for check box.
* @param contentColor the color of the check mark.
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
* @see CheckBoxDefaults.colors
*/
@Immutable
data class CheckBoxColors(
val contentColor: Color,
val inactiveColor: Color,
val activeColor: Color
val activeColor: Color,
val borderColor: Color
)
/**
* Style defines for check box.
* @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 border the border stroke.
* @see CheckBoxDefaults.style
*/
@Immutable
data class CheckBoxStyle(
@@ -91,7 +85,7 @@ data class CheckBoxStyle(
val pressedGain: Float,
val hoveredGain: Float,
val shape: Shape,
val border: BorderStroke
val borderWidth: Dp
)
/**
@@ -110,8 +104,8 @@ fun CheckBox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
colors: CheckBoxColors = CheckBoxDefaults.colors,
style: CheckBoxStyle = CheckBoxDefaults.style,
colors: CheckBoxColors = CheckBoxDefaults.colors(),
style: CheckBoxStyle = CheckBoxDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null
@@ -133,7 +127,7 @@ fun CheckBox(
.size(style.strokeSize)
.scale(animatedStrokeScale)
.background(animatedColor, style.shape)
.borderOrElse(style.border, style.shape),
.borderOrElse(style.borderWidth, colors.borderColor, style.shape),
contentAlignment = Alignment.Center
) {
Icon(
@@ -146,7 +140,7 @@ fun CheckBox(
scaleY = animatedContentLayer
),
imageVector = FlexiIcons.CheckMark,
style = IconDefaults.style.copy(tint = colors.contentColor)
style = IconDefaults.style(tint = colors.contentColor)
)
}
content?.also { content ->
@@ -162,44 +156,70 @@ fun CheckBox(
* Defaults of check box.
*/
object CheckBoxDefaults {
val colors: CheckBoxColors
@Composable
@ReadOnlyComposable
get() = defaultCheckBoxColors()
val style: CheckBoxStyle
@Composable
@ReadOnlyComposable
get() = defaultCheckBoxStyle()
/**
* 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
fun colors(
contentColor: Color = CheckBoxProperties.ContentColor,
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
fun style(
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
@ReadOnlyComposable
private fun defaultCheckBoxColors() = CheckBoxColors(
contentColor = Color.White,
inactiveColor = LocalColors.current.themeTertiary,
activeColor = LocalColors.current.themePrimary
)
@Composable
@ReadOnlyComposable
private fun defaultCheckBoxStyle() = CheckBoxStyle(
contentSpacing = LocalSizes.current.spacingSecondary,
contentSize = DefaultContentSize,
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
@Stable
internal object CheckBoxProperties {
val ContentColor = Color.White
val InactiveColor = ColorsDescriptor.ThemeTertiary
val ActiveColor = ColorsDescriptor.ThemePrimary
val BorderColor = ColorsDescriptor.TextPrimary
val ContentSpacing = SizesDescriptor.SpacingSecondary
val ContentSize = 13.dp
val StrokeSize = 20.dp
const val PressedGain = 0.9f
const val HoveredGain = 1.1f
val Shape: Shape = RoundedCornerShape(4.dp)
val BorderWidth = SizesDescriptor.BorderSizeTertiary
}

View File

@@ -19,7 +19,7 @@
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
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.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
@@ -60,7 +59,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
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.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
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.componentState
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.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.resources.FlexiIcons
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.min
/**
* Colors defines for dropdown list.
* @param endIconInactiveTint the tint of the end icon when inactive.
* @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.
* @see DropdownListDefaults.colors
*/
@Immutable
data class DropdownListColors(
val endIconInactiveTint: Color,
val endIconActiveTint: Color,
val backgroundColor: Color,
val borderInactiveColor: Color,
val borderActiveColor: Color,
val backgroundColor: Color
val borderActiveColor: Color
)
/**
* Colors defines for dropdown menu.
* @param contentColor the color of the content.
* @param activeColor the color of the active item.
* @param borderColor the color of the border.
* @see DropdownMenuDefaults.colors
*/
@Immutable
data class DropdownMenuColors(
val contentColor: Color,
val activeColor: Color,
val backgroundColor: Color,
val borderColor: Color
)
/**
* Style defines for dropdown list.
* @param padding the padding of the content.
* @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.
* @see DropdownListDefaults.style
*/
@Immutable
data class DropdownListStyle(
val padding: ComponentPadding,
val shape: Shape,
val endIconSize: Dp,
val borderInactive: BorderStroke,
val borderActive: BorderStroke
val borderInactiveWidth: Dp,
val borderActiveWidth: Dp
)
/**
* Style defines for dropdown menu.
* @param inTransitionDuration the duration of the in transition.
* @param outTransitionDuration the duration of the out transition.
* @param contentStyle the content style of area box.
* @param borderStyle the brder style of area box.
* @see DropdownMenuDefaults.style
*/
@Immutable
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 outTransitionDuration: Int,
val contentStyle: AreaBoxStyle,
val borderStyle: AreaBoxStyle
val outTransitionDuration: Int
)
/**
@@ -202,10 +194,10 @@ fun DropdownList(
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
colors: DropdownListColors = DropdownListDefaults.colors,
style: DropdownListStyle = DropdownListDefaults.style,
menuColors: DropdownMenuColors = DropdownMenuDefaults.colors,
menuStyle: DropdownMenuStyle = DropdownMenuDefaults.style,
colors: DropdownListColors = DropdownListDefaults.colors(),
style: DropdownListStyle = DropdownListDefaults.style(),
menuColors: DropdownMenuColors = DropdownMenuDefaults.colors(),
menuStyle: DropdownMenuStyle = DropdownMenuDefaults.style(),
enabled: Boolean = true,
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
@@ -221,24 +213,21 @@ fun DropdownList(
else -> colors.endIconInactiveTint
})
val animatedBorderColor by animateColorAsState(when {
focused || hovered -> style.borderActive.solidColor
else -> style.borderInactive.solidColor
focused || hovered -> colors.borderActiveColor
else -> colors.borderInactiveColor
})
val animatedDirection by animateFloatAsState(if (expanded) 180f else 0f)
val animatedBorderWidth by animateDpAsState(when {
focused -> style.borderActive.width
else -> style.borderInactive.width
focused -> style.borderActiveWidth
else -> style.borderInactiveWidth
})
val border = when {
focused || hovered -> style.borderInactive
else -> style.borderInactive
}.copy(animatedBorderWidth, SolidColor(animatedBorderColor))
DropdownMenuBox(
modifier = Modifier.dropdownList(
enabled = enabled,
colors = colors,
style = style,
border = border,
borderColor = animatedBorderColor,
borderWidth = animatedBorderWidth,
focusRequester = focusRequester,
interactionSource = interactionSource,
then = modifier.rippleClickable(
@@ -265,7 +254,7 @@ fun DropdownList(
rotationZ = animatedDirection
}.size(style.endIconSize),
imageVector = FlexiIcons.Dropdown,
style = IconDefaults.style.copy(tint = animatedEndIconTint)
style = IconDefaults.style(tint = animatedEndIconTint)
)
}
DropdownMenu(
@@ -301,8 +290,8 @@ fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
colors: DropdownMenuColors = DropdownMenuDefaults.colors,
style: DropdownMenuStyle = DropdownMenuDefaults.style,
colors: DropdownMenuColors = DropdownMenuDefaults.colors(),
style: DropdownMenuStyle = DropdownMenuDefaults.style(),
offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
@@ -383,7 +372,8 @@ fun DropdownMenuBox(
* @param modifier the [Modifier] to be applied to this dropdown menu item.
* @param contentColor the color of the content.
* @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 actived whether the dropdown menu item is actived, default is false.
* @param interactionSource the interaction source of the dropdown menu item.
@@ -395,7 +385,8 @@ fun DropdownMenuItem(
modifier: Modifier = Modifier,
contentColor: Color = Color.Unspecified,
activeColor: Color = Color.Unspecified,
contentStyle: AreaBoxStyle? = null,
contentPadding: ComponentPadding? = null,
contentShape: Shape? = null,
enabled: Boolean = true,
actived: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@@ -403,13 +394,16 @@ fun DropdownMenuItem(
) {
val currentColor = contentColor.orNull()
?: LocalDropdownMenuContentColor.current.orNull()
?: DropdownMenuDefaults.colors.contentColor
?: DropdownMenuDefaults.colors().contentColor
val currentActiveColor = activeColor.orNull()
?: LocalDropdownMenuActiveColor.current.orNull()
?: DropdownMenuDefaults.colors.activeColor
val currentStyle = contentStyle
?: LocalDropdownMenuContentStyle.current
?: DropdownMenuDefaults.style.contentStyle
?: DropdownMenuDefaults.colors().activeColor
val currentPadding = contentPadding
?: LocalDropdownMenuContentPadding.current
?: DropdownMenuDefaults.style().contentPadding
val currentShape = contentShape
?: LocalDropdownMenuContentShape.current
?: DropdownMenuDefaults.style().contentShape
AreaRow(
modifier = Modifier.componentState(enabled)
.then(modifier)
@@ -425,8 +419,8 @@ fun DropdownMenuItem(
interactionSource = interactionSource,
onClick = onClick
),
color = if (actived) currentActiveColor else Color.Transparent,
style = currentStyle,
colors = AreaBoxDefaults.colors(backgroundColor = if (actived) currentActiveColor else Color.Transparent),
style = AreaBoxDefaults.style(padding = currentPadding, shape = currentShape),
verticalAlignment = Alignment.CenterVertically
) {
CompositionLocalProvider(
@@ -489,13 +483,22 @@ private fun DropdownMenuContent(
transformOrigin = transformOriginState.value
}
},
color = colors.borderColor,
style = style.borderStyle
colors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) {
CompositionLocalProvider(
LocalDropdownMenuContentColor provides colors.contentColor,
LocalDropdownMenuActiveColor provides colors.activeColor,
LocalDropdownMenuContentStyle provides style.contentStyle
LocalDropdownMenuContentPadding provides style.contentPadding,
LocalDropdownMenuContentShape provides style.contentShape
) { content() }
}
}
@@ -504,7 +507,8 @@ private fun Modifier.dropdownList(
enabled: Boolean,
colors: DropdownListColors,
style: DropdownListStyle,
border: BorderStroke,
borderColor: Color,
borderWidth: Dp,
focusRequester: FocusRequester,
interactionSource: MutableInteractionSource,
then: Modifier
@@ -514,7 +518,8 @@ private fun Modifier.dropdownList(
properties["enabled"] = enabled
properties["colors"] = colors
properties["style"] = style
properties["border"] = border
properties["borderColor"] = borderColor
properties["borderWidth"] = borderWidth
}
) {
componentState(enabled)
@@ -523,7 +528,7 @@ private fun Modifier.dropdownList(
.hoverable(interactionSource, enabled)
.clip(style.shape)
.background(colors.backgroundColor, style.shape)
.borderOrElse(border, style.shape)
.borderOrElse(borderWidth, borderColor, style.shape)
.then(then)
.padding(style.padding)
}
@@ -611,92 +616,150 @@ private data class DropdownMenuPositionProvider(
* Defaults of dropdown list.
*/
object DropdownListDefaults {
val colors: DropdownListColors
@Composable
@ReadOnlyComposable
get() = defaultDropdownListColors()
val style: DropdownListStyle
@Composable
@ReadOnlyComposable
get() = defaultDropdownListStyle()
/**
* 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
fun colors(
endIconInactiveTint: Color = DropdownListProperties.EndIconInactiveTint.toColor(),
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
fun style(
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.
*/
object DropdownMenuDefaults {
val colors: DropdownMenuColors
@Composable
@ReadOnlyComposable
get() = defaultDropdownMenuColors()
val style: DropdownMenuStyle
@Composable
@ReadOnlyComposable
get() = defaultDropdownMenuStyle()
/**
* 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
fun colors(
contentColor: Color = DropdownMenuProperties.ContentColor.toColor(),
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
fun style(
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 LocalDropdownMenuContentStyle = compositionLocalOf<AreaBoxStyle?> { null }
@Composable
@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 LocalDropdownMenuContentColor = compositionLocalOf { Color.Unspecified }
private val LocalDropdownMenuContentPadding = compositionLocalOf<ComponentPadding?> { null }
private val LocalDropdownMenuContentShape = compositionLocalOf<Shape?> { null }
private val MenuItemMinWidth = 112.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.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
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.unit.Dp
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.isUnspecified
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.
* @param size the size.
* @param tint the tint.
* Style defines for basic icon.
* @see IconDefaults.style
*/
@Immutable
data class IconStyle(
@@ -67,14 +68,14 @@ data class IconStyle(
* @param imageVector the vector image to be drawn.
* @param contentDescription the content description for 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
fun Icon(
imageVector: ImageVector,
contentDescription: String? = null,
modifier: Modifier = Modifier,
style: IconStyle = IconDefaults.style
style: IconStyle? = null
) {
val painter = rememberVectorPainter(imageVector)
Icon(painter, contentDescription, modifier, style)
@@ -85,16 +86,23 @@ fun Icon(
* @param painter the painter to be drawn.
* @param contentDescription the content description for 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
fun Icon(
painter: Painter,
contentDescription: String? = null,
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)
Modifier.semantics {
this.contentDescription = contentDescription
@@ -103,7 +111,7 @@ fun Icon(
else Modifier
Box(
modifier = modifier.toolingGraphicsLayer()
.defaultSizeFor(style, painter)
.defaultSizeFor(currentStyle, painter)
.paint(
painter,
colorFilter = colorFilter,
@@ -127,30 +135,49 @@ private fun Modifier.defaultSizeFor(
style.size.isSpecified ||
painter.intrinsicSize == Size.Unspecified ||
painter.intrinsicSize.isInfinite() ->
Modifier.size(style.size.orNull() ?: defaultIconSize())
Modifier.size(style.size.orNull() ?: IconDefaults.style().size)
else -> Modifier
})
}
/**
* Defaults of icon.
* Defaults of basic icon.
*/
object IconDefaults {
val style: IconStyle
@Composable
@ReadOnlyComposable
get() = LocalIconStyle.current
/**
* Creates a [IconStyle] with the default values.
* @param size the size.
* @param tint the tint.
* @return [IconStyle]
*/
@Composable
fun style(
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(
size = Dp.Unspecified,
tint = Color.Unspecified
)
@Composable
@ReadOnlyComposable
private fun defaultIconSize() = LocalSizes.current.iconSizePrimary
private fun IconStyle.orNull() = if (size.isUnspecified && tint.isUnspecified) null else this
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.LocalTypography
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.resources.FlexiIcons
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.
* @param backgroundColor the background color.
* @param titleTextColor the title text color.
* @param subTextColor the sub text color.
* @param arrowIconTint the arrow icon tint.
* @see ItemBoxDefaults.colors
*/
@Immutable
data class ItemBoxColors(
val backgroundColor: Color,
val titleTextColor: Color,
val subTextColor: Color,
val arrowIconTint: Color
val arrowIconTint: Color,
val borderColor: Color
)
/**
* Style defines for item box.
* @param boxStyle the style of area box.
* @param contentSpacing the spacing between the components of content.
* @param titleTextStyle the title text style.
* @param subTextStyle the sub text style.
* @see ItemBoxDefaults.style
*/
@Immutable
data class ItemBoxStyle(
val boxStyle: AreaBoxStyle,
val contentSpacing: Dp,
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(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ItemBoxColors = ItemBoxDefaults.colors,
style: ItemBoxStyle = ItemBoxDefaults.style,
colors: ItemBoxColors = ItemBoxDefaults.colors(),
style: ItemBoxStyle = ItemBoxDefaults.style(),
enabled: Boolean = true,
showArrowIcon: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@@ -112,8 +114,15 @@ fun HorizontalItemBox(
onClick = onClick
),
initializer = { componentState(enabled) },
color = colors.backgroundColor,
style = style.boxStyle
colors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -134,7 +143,7 @@ fun HorizontalItemBox(
}
if (showArrowIcon) Icon(
imageVector = FlexiIcons.ArrowForward,
style = IconDefaults.style.copy(
style = IconDefaults.style(
size = DefaultArrowIconSize,
tint = colors.arrowIconTint
)
@@ -160,8 +169,8 @@ fun HorizontalItemBox(
fun VerticalItemBox(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ItemBoxColors = ItemBoxDefaults.colors,
style: ItemBoxStyle = ItemBoxDefaults.style,
colors: ItemBoxColors = ItemBoxDefaults.colors(),
style: ItemBoxStyle = ItemBoxDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
logoImage: @Composable (() -> Unit)? = null,
@@ -175,8 +184,15 @@ fun VerticalItemBox(
onClick = onClick
),
initializer = { componentState(enabled) },
color = colors.backgroundColor,
style = style.boxStyle,
colors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(style.contentSpacing / VerticalContentSpacingRatio)
) {
@@ -213,31 +229,73 @@ private fun ItemBoxContent(
* Defaults of item box.
*/
object ItemBoxDefaults {
val colors: ItemBoxColors
@Composable
get() = defaultItemBoxColors()
val style: ItemBoxStyle
@Composable
get() = defaultItemBoxStyle()
/**
* 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
fun colors(
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
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
@ReadOnlyComposable
private fun defaultItemBoxColors() = ItemBoxColors(
backgroundColor = AreaBoxDefaults.color,
titleTextColor = LocalColors.current.textPrimary,
subTextColor = LocalColors.current.textSecondary,
arrowIconTint = LocalColors.current.textSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultItemBoxStyle() = ItemBoxStyle(
boxStyle = AreaBoxDefaults.style,
contentSpacing = LocalSizes.current.spacingSecondary,
titleTextStyle = LocalTypography.current.primary,
subTextStyle = LocalTypography.current.secondary
)
@Stable
internal object ItemBoxProperties {
val BackgroundColor = AreaBoxProperties.BackgroundColor
val TitleTextColor = ColorsDescriptor.TextPrimary
val SubTextColor = ColorsDescriptor.TextSecondary
val ArrowIconTint = ColorsDescriptor.TextSecondary
val BorderColor = AreaBoxProperties.BorderColor
val ContentSpacing = SizesDescriptor.SpacingSecondary
val TitleTextStyle = TypographyDescriptor.Primary
val SubTextStyle = TypographyDescriptor.Secondary
val Shape = AreaBoxProperties.Shape
val BorderWidth = AreaBoxProperties.BorderWidth
val ShadowSize = AreaBoxProperties.ShadowSize
}
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
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.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.InteractionDefaults
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.
* @param backgroundColor the background color.
* @param indicatorColor the indicator color.
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
* @see NavigationBarDefaults.colors
*/
@Immutable
data class NavigationBarColors(
val backgroundColor: Color,
val borderColor: Color,
val indicatorColor: Color,
val selectedContentColor: Color,
val unselectedContentColor: Color
@@ -80,14 +82,14 @@ data class NavigationBarColors(
/**
* Style defines for navigation bar.
* @param boxStyle the style of area box.
* @param contentSpacing the spacing between the components of content.
* @param contentPadding the padding of content.
* @param contentShape the content shape.
* @see NavigationBarDefaults.style
*/
@Immutable
data class NavigationBarStyle(
val boxStyle: AreaBoxStyle,
val padding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val shadowSize: Dp,
val contentSpacing: Dp,
val contentPadding: ComponentPadding,
val contentShape: Shape
@@ -106,16 +108,24 @@ data class NavigationBarStyle(
@Composable
fun NavigationBarRow(
modifier: Modifier = Modifier,
colors: NavigationBarColors = NavigationBarDefaults.colors,
style: NavigationBarStyle = NavigationBarDefaults.style,
colors: NavigationBarColors = NavigationBarDefaults.colors(),
style: NavigationBarStyle = NavigationBarDefaults.style(),
arrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
content: @Composable RowScope.() -> Unit
) {
NavigationBarStyleBox(modifier, horizontal = true, colors, style) {
AreaRow(
modifier = Modifier.fillMaxWidth().selectableGroup(),
color = colors.backgroundColor,
style = style.boxStyle,
colors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalArrangement = arrangement,
verticalAlignment = Alignment.CenterVertically,
content = content
@@ -136,16 +146,24 @@ fun NavigationBarRow(
@Composable
fun NavigationBarColumn(
modifier: Modifier = Modifier,
colors: NavigationBarColors = NavigationBarDefaults.colors,
style: NavigationBarStyle = NavigationBarDefaults.style,
colors: NavigationBarColors = NavigationBarDefaults.colors(),
style: NavigationBarStyle = NavigationBarDefaults.style(),
arrangement: Arrangement.Vertical = Arrangement.SpaceBetween,
content: @Composable ColumnScope.() -> Unit
) {
NavigationBarStyleBox(modifier, horizontal = false, colors, style) {
AreaColumn(
modifier = Modifier.fillMaxWidth().selectableGroup(),
color = colors.backgroundColor,
style = style.boxStyle,
colors = AreaBoxDefaults.colors(
backgroundColor = colors.backgroundColor,
borderColor = colors.borderColor
),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = arrangement,
content = content
@@ -186,16 +204,16 @@ fun NavigationBarItem(
text: @Composable (() -> Unit)? = null
) {
val currentHorizontal = horizontal ?: LocalHorizontalNavigationBar.current
val currentColors = colors ?: LocalNavigationBarColors.current ?: NavigationBarDefaults.colors
val currentColors = colors ?: LocalNavigationBarColors.current ?: NavigationBarDefaults.colors()
val currentContentSpacing = contentSpacing.orNull()
?: LocalNavigationBarContentSpacing.current.orNull()
?: NavigationBarDefaults.style.contentSpacing
?: NavigationBarDefaults.style().contentSpacing
val currentContentPadding = contentPadding
?: LocalNavigationBarContentPadding.current
?: NavigationBarDefaults.style.contentPadding
?: NavigationBarDefaults.style().contentPadding
val currentContentShape = contentShape
?: LocalNavigationBarContentShape.current
?: NavigationBarDefaults.style.contentShape
?: NavigationBarDefaults.style().contentShape
val animatedIndicatorColor by animateColorAsState(if (selected) currentColors.indicatorColor else Color.Transparent)
val animatedContentColor by animateColorAsState(if (selected) currentColors.selectedContentColor else currentColors.unselectedContentColor)
val currentIconStyle = LocalIconStyle.current.copy(tint = animatedContentColor)
@@ -206,7 +224,7 @@ fun NavigationBarItem(
.then(modifier)
.background(animatedIndicatorColor)
.rippleClickable(
rippleStyle = InteractionDefaults.rippleStyle.copy(color = currentColors.indicatorColor),
rippleStyle = InteractionDefaults.rippleStyle(color = currentColors.indicatorColor),
enabled = enabled,
role = Role.Tab,
interactionSource = interactionSource,
@@ -275,45 +293,85 @@ private fun NavigationBarStyleBox(
* Defaults of navigation bar.
*/
object NavigationBarDefaults {
val colors: NavigationBarColors
@Composable
@ReadOnlyComposable
get() = defaultNavigationBarColors()
val style: NavigationBarStyle
@Composable
@ReadOnlyComposable
get() = defaultNavigationBarStyle()
/**
* 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
fun colors(
backgroundColor: Color = NavigationBarProperties.BackgroundColor.toColor(),
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
fun style(
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 LocalNavigationBarColors = compositionLocalOf<NavigationBarColors?> { null }
private val LocalNavigationBarContentSpacing = compositionLocalOf { Dp.Unspecified }
private val LocalNavigationBarContentPadding = compositionLocalOf<PaddingValues?> { null }
private val LocalNavigationBarContentPadding = compositionLocalOf<ComponentPadding?> { 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

View File

@@ -19,7 +19,7 @@
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ObjectPropertyName")
package com.highcapable.flexiui.component
@@ -37,7 +37,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
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.LayoutDirection
import androidx.compose.ui.unit.dp
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.toColor
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.max
/**
* Style interface for progress indicator.
* @see CircularIndicatorStyle
* @see LinearIndicatorStyle
*/
@Stable
interface ProgressIndicatorStyle {
@@ -69,40 +70,45 @@ interface ProgressIndicatorStyle {
/**
* Animation interface for progress indicator.
* @see CircularIndicatorAnimation
* @see LinearIndicatorAnimation
*/
@Stable
interface ProgressIndicatorAnimation {
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.
* @param strokeWidth the stroke width of indicator.
* @param strokeCap the stroke cap of indicator.
* @param radius the radius of indicator.
* @param animation the animation of indicator.
* @see CircularIndicatorDefaults.style
*/
@Immutable
data class CircularIndicatorStyle(
override val strokeWidth: Dp,
override val strokeCap: StrokeCap,
val radius: Dp,
val animation: CircularIndicatorAnimation
val radius: Dp
) : ProgressIndicatorStyle
/**
* Style defines for linear progress indicator.
* @param strokeWidth the stroke width of indicator.
* @param strokeCap the stroke cap of indicator.
* @param width the width of indicator.
* @param animation the animation of indicator.
* @see LinearIndicatorDefaults.style
*/
@Immutable
data class LinearIndicatorStyle(
override val strokeWidth: Dp,
override val strokeCap: StrokeCap,
val width: Dp,
val animation: LinearIndicatorAnimation
val width: Dp
) : ProgressIndicatorStyle
/**
@@ -147,17 +153,6 @@ data class LinearIndicatorAnimation(
val secondLineTailDelay: Int
) : 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.
* @see LinearProgressIndicator
@@ -166,8 +161,9 @@ data class ProgressIndicatorColors(
* @param min the min of indicator, default is 0f.
* @param max the max of indicator, default is 1f.
* @param indeterminate the indeterminate of indicator, default is false.
* @param colors the colors of indicator, default is [CircularIndicatorDefaults.colors].
* @param style the style of indicator, default is [CircularIndicatorDefaults.style].
* @param animation the animation of indicator.
* @param colors the colors of indicator.
* @param style the style of indicator.
*/
@Composable
fun CircularProgressIndicator(
@@ -176,11 +172,14 @@ fun CircularProgressIndicator(
min: Float = 0f,
max: Float = 1f,
indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = CircularIndicatorDefaults.colors,
style: CircularIndicatorStyle = CircularIndicatorDefaults.style
animation: CircularIndicatorAnimation = DefaultCircularIndicatorAnimation,
colors: ProgressIndicatorColors? = null,
style: CircularIndicatorStyle? = null
) {
val diameter = style.radius * 2
val stroke = with(LocalDensity.current) { Stroke(width = style.strokeWidth.toPx(), cap = style.strokeCap) }
val currentColors = colors ?: LocalProgressIndicatorColors.current ?: CircularIndicatorDefaults.colors()
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. */
@Composable
@@ -190,8 +189,8 @@ fun CircularProgressIndicator(
Canvas(modifier.progressSemantics(normalizedProgress).size(diameter)) {
val startAngle = 270f
val sweep = normalizedProgress * 360f
drawCircularIndicatorBackground(colors.backgroundColor, stroke)
drawCircularIndicator(startAngle, sweep, colors.foregroundColor, stroke)
drawCircularIndicatorBackground(currentColors.backgroundColor, stroke)
drawCircularIndicator(startAngle, sweep, currentColors.foregroundColor, stroke)
}
}
@@ -201,55 +200,55 @@ fun CircularProgressIndicator(
val transition = rememberInfiniteTransition()
val currentRotation by transition.animateValue(
initialValue = 0,
style.animation.rotationsPerCycle,
animation.rotationsPerCycle,
Int.VectorConverter,
infiniteRepeatable(
animation = tween(
durationMillis = style.animation.duration * style.animation.rotationsPerCycle,
durationMillis = animation.duration * animation.rotationsPerCycle,
easing = LinearEasing
)
)
)
val baseRotation by transition.animateFloat(
initialValue = 0f,
style.animation.baseRotationAngle,
animation.baseRotationAngle,
infiniteRepeatable(
animation = tween(
durationMillis = style.animation.duration,
durationMillis = animation.duration,
easing = LinearEasing
)
)
)
val headAndTailAnimationDuration = caleHeadAndTailAnimationDuration(style.animation.duration)
val headAndTailAnimationDuration = caleHeadAndTailAnimationDuration(animation.duration)
val endAngle by transition.animateFloat(
initialValue = 0f,
style.animation.jumpRotationAngle,
animation.jumpRotationAngle,
infiniteRepeatable(
animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2
0f at 0 with CircularEasing
style.animation.jumpRotationAngle at headAndTailAnimationDuration
animation.jumpRotationAngle at headAndTailAnimationDuration
}
)
)
val startAngle by transition.animateFloat(
initialValue = 0f,
style.animation.jumpRotationAngle,
animation.jumpRotationAngle,
infiniteRepeatable(
animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2
0f at headAndTailAnimationDuration with CircularEasing
style.animation.jumpRotationAngle at durationMillis
animation.jumpRotationAngle at durationMillis
}
)
)
Canvas(modifier.progressSemantics().size(diameter)) {
drawCircularIndicatorBackground(colors.backgroundColor, stroke)
val rotationAngleOffset = caleRotationAngleOffset(style.animation.baseRotationAngle, style.animation.jumpRotationAngle)
drawCircularIndicatorBackground(currentColors.backgroundColor, stroke)
val rotationAngleOffset = caleRotationAngleOffset(animation.baseRotationAngle, animation.jumpRotationAngle)
val currentRotationAngleOffset = (currentRotation * rotationAngleOffset) % 360f
val sweep = abs(endAngle - startAngle)
val offset = style.animation.startAngleOffset + currentRotationAngleOffset + baseRotation
drawIndeterminateCircularIndicator(startAngle + offset, style.strokeWidth, diameter, sweep, colors.foregroundColor, stroke)
val offset = animation.startAngleOffset + currentRotationAngleOffset + baseRotation
drawIndeterminateCircularIndicator(startAngle + offset, currentStyle.strokeWidth, diameter, sweep, currentColors.foregroundColor, stroke)
}
}
if (indeterminate) Indeterminate() else Determinate()
@@ -263,8 +262,9 @@ fun CircularProgressIndicator(
* @param min the min of indicator, default is 0f.
* @param max the max of indicator, default is 1f.
* @param indeterminate the indeterminate of indicator, default is false.
* @param colors the colors of indicator, default is [LinearIndicatorDefaults.colors].
* @param style the style of indicator, default is [LinearIndicatorDefaults.style].
* @param animation the animation of indicator.
* @param colors the colors of indicator.
* @param style the style of indicator.
*/
@Composable
fun LinearProgressIndicator(
@@ -273,18 +273,22 @@ fun LinearProgressIndicator(
min: Float = 0f,
max: Float = 1f,
indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = LinearIndicatorDefaults.colors,
style: LinearIndicatorStyle = LinearIndicatorDefaults.style
animation: LinearIndicatorAnimation = DefaultLinearIndicatorAnimation,
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. */
@Composable
fun Determinate() {
val coercedProgress = progress.coerceIn(min, max)
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
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap)
drawLinearIndicator(startFraction = 0f, normalizedProgress, colors.foregroundColor, strokeWidth, style.strokeCap)
drawLinearIndicatorBackground(currentColors.backgroundColor, strokeWidth, currentStyle.strokeCap)
drawLinearIndicator(startFraction = 0f, normalizedProgress, currentColors.foregroundColor, strokeWidth, currentStyle.strokeCap)
}
}
@@ -297,9 +301,9 @@ fun LinearProgressIndicator(
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animation.duration
0f at style.animation.firstLineHeadDelay with FirstLineHeadEasing
1f at style.animation.firstLineHeadDuration + style.animation.firstLineHeadDelay
durationMillis = animation.duration
0f at animation.firstLineHeadDelay with FirstLineHeadEasing
1f at animation.firstLineHeadDuration + animation.firstLineHeadDelay
}
)
)
@@ -308,9 +312,9 @@ fun LinearProgressIndicator(
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animation.duration
0f at style.animation.firstLineTailDelay with FirstLineTailEasing
1f at style.animation.firstLineTailDuration + style.animation.firstLineTailDelay
durationMillis = animation.duration
0f at animation.firstLineTailDelay with FirstLineTailEasing
1f at animation.firstLineTailDuration + animation.firstLineTailDelay
}
)
)
@@ -319,9 +323,9 @@ fun LinearProgressIndicator(
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animation.duration
0f at style.animation.secondLineHeadDelay with SecondLineHeadEasing
1f at style.animation.secondLineHeadDuration + style.animation.secondLineHeadDelay
durationMillis = animation.duration
0f at animation.secondLineHeadDelay with SecondLineHeadEasing
1f at animation.secondLineHeadDuration + animation.secondLineHeadDelay
}
)
)
@@ -330,19 +334,19 @@ fun LinearProgressIndicator(
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animation.duration
0f at style.animation.secondLineTailDelay with SecondLineTailEasing
1f at style.animation.secondLineTailDuration + style.animation.secondLineTailDelay
durationMillis = animation.duration
0f at animation.secondLineTailDelay with SecondLineTailEasing
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
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap)
drawLinearIndicatorBackground(currentColors.backgroundColor, strokeWidth, currentStyle.strokeCap)
if (firstLineHead - firstLineTail > 0)
drawLinearIndicator(firstLineHead, firstLineTail, colors.foregroundColor, strokeWidth, style.strokeCap)
drawLinearIndicator(firstLineHead, firstLineTail, currentColors.foregroundColor, strokeWidth, currentStyle.strokeCap)
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()
@@ -422,100 +426,107 @@ private fun DrawScope.drawLinearIndicator(
* Defaults of circular progress indicator.
*/
object CircularIndicatorDefaults {
val colors: ProgressIndicatorColors
@Composable
@ReadOnlyComposable
get() = LocalProgressIndicatorColors.current.copy(
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orNull()
?: defaultCircularIndicatorColors().foregroundColor,
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orNull()
?: defaultCircularIndicatorColors().backgroundColor
)
val style: CircularIndicatorStyle
@Composable
@ReadOnlyComposable
get() = defaultCircularIndicatorStyle()
/**
* Creates a [ProgressIndicatorColors] with the default values.
* @param foregroundColor the foreground color of indicator.
* @param backgroundColor the background color of indicator.
* @return [ProgressIndicatorColors]
*/
@Composable
fun colors(
foregroundColor: Color = CircularIndicatorProperties.ForegroundColor.toColor(),
backgroundColor: Color = CircularIndicatorProperties.BackgroundColor
) = ProgressIndicatorColors(
foregroundColor = foregroundColor,
backgroundColor = backgroundColor
)
/**
* 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
fun style(
strokeWidth: Dp = CircularIndicatorProperties.StrokeWidth,
strokeCap: StrokeCap = CircularIndicatorProperties._StrokeCap,
radius: Dp = CircularIndicatorProperties.Radius
) = CircularIndicatorStyle(
strokeWidth = strokeWidth,
strokeCap = strokeCap,
radius = radius
)
}
/**
* Defaults of linear progress indicator.
*/
object LinearIndicatorDefaults {
val colors: ProgressIndicatorColors
@Composable
@ReadOnlyComposable
get() = LocalProgressIndicatorColors.current.copy(
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orNull()
?: defaultLinearIndicatorColors().foregroundColor,
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orNull()
?: defaultLinearIndicatorColors().backgroundColor
)
val style: LinearIndicatorStyle
@Composable
@ReadOnlyComposable
get() = defaultLinearIndicatorStyle()
/**
* Creates a [ProgressIndicatorColors] with the default values.
* @param foregroundColor the foreground color of indicator.
* @param backgroundColor the background color of indicator.
* @return [ProgressIndicatorColors]
*/
@Composable
fun colors(
foregroundColor: Color = LinearIndicatorProperties.ForegroundColor.toColor(),
backgroundColor: Color = LinearIndicatorProperties.BackgroundColor.toColor()
) = ProgressIndicatorColors(
foregroundColor = foregroundColor,
backgroundColor = backgroundColor
)
/**
* 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
fun style(
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
@ReadOnlyComposable
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
)
)
internal val LocalProgressIndicatorColors = compositionLocalOf<ProgressIndicatorColors?> { null }
internal val LocalProgressIndicatorStyle = compositionLocalOf<ProgressIndicatorStyle?> { null }
private fun caleRotationAngleOffset(baseRotationAngle: Float, jumpRotationAngle: Float) = (baseRotationAngle + jumpRotationAngle) % 360f
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 DefaultRotationAnimationDuration = 1332
private const val DefaultFirstLineHeadDuration = 750
private const val DefaultFirstLineTailDuration = 850
@@ -528,7 +539,6 @@ private const val DefaultSecondLineHeadDelay = 1000
private const val DefaultSecondLineTailDelay = 1267
private const val DefaultRotationsPerCycle = 5
private const val DefaultRotationDuration = 1332
private const val DefaultStartAngleOffset = -90f
private const val DefaultBaseRotationAngle = 286f
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 SecondLineTailEasing = CubicBezierEasing(0.1f, 0f, 0.45f, 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.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
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.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -54,32 +53,28 @@ 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.componentState
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.ShapesDescriptor
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.
* @param contentColor the color of the check mark.
* @param inactiveColor the color of the unchecked box.
* @param activeColor the color of the checked box.
* @see RadioButtonDefaults.colors
*/
@Immutable
data class RadioButtonColors(
val contentColor: Color,
val inactiveColor: Color,
val activeColor: Color
val activeColor: Color,
val borderColor: Color
)
/**
* Style defines for radio button.
* @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 border the border stroke.
* @see RadioButtonDefaults.style
*/
@Immutable
data class RadioButtonStyle(
@@ -90,7 +85,7 @@ data class RadioButtonStyle(
val pressedGain: Float,
val hoveredGain: Float,
val shape: Shape,
val border: BorderStroke
val borderWidth: Dp
)
/**
@@ -109,8 +104,8 @@ fun RadioButton(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: RadioButtonColors = RadioButtonDefaults.colors,
style: RadioButtonStyle = RadioButtonDefaults.style,
colors: RadioButtonColors = RadioButtonDefaults.colors(),
style: RadioButtonStyle = RadioButtonDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null
@@ -157,45 +152,73 @@ fun RadioButton(
* Defaults of radio button.
*/
object RadioButtonDefaults {
val colors: RadioButtonColors
@Composable
@ReadOnlyComposable
get() = defaultRadioButtonColors()
val style: RadioButtonStyle
@Composable
@ReadOnlyComposable
get() = defaultRadioButtonStyle()
/**
* 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
fun colors(
contentColor: Color = RadioButtonProperties.ContentColor,
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
fun style(
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
@ReadOnlyComposable
private fun defaultRadioButtonColors() = RadioButtonColors(
contentColor = Color.White,
inactiveColor = LocalColors.current.themeTertiary,
activeColor = LocalColors.current.themePrimary
)
@Composable
@ReadOnlyComposable
private fun defaultRadioButtonStyle() = RadioButtonStyle(
contentSpacing = LocalSizes.current.spacingSecondary,
contentRadius = DefaultContentRadius,
contentShadowSize = DefaultContentShadowSize,
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
@Stable
internal object RadioButtonProperties {
val ContentColor = Color.White
val InactiveColor = ColorsDescriptor.ThemeTertiary
val ActiveColor = ColorsDescriptor.ThemePrimary
val BorderColor = ColorsDescriptor.TextPrimary
val ContentSpacing = SizesDescriptor.SpacingSecondary
val ContentRadius = 5.dp
val ContentShadowSize = 0.5.dp
val StrokeRadius = 10.dp
const val PressedGain = 0.9f
const val HoveredGain = 1.2f
val Shape = ShapesDescriptor.Tertiary
val BorderWidth = SizesDescriptor.BorderSizeTertiary
}

View File

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

View File

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

View File

@@ -31,19 +31,22 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.debugInspectorInfo
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.flexiui.ColorsDescriptor
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.
* @param contentColor the content color, usually for the text color.
* @param backgroundColor the background color.
* @see SurfaceDefaults.colors
*/
@Immutable
data class SurfaceColors(
@@ -65,7 +68,7 @@ data class SurfaceColors(
fun Surface(
modifier: Modifier = Modifier,
initializer: @Composable Modifier.() -> Modifier = { Modifier },
colors: SurfaceColors = SurfaceDefaults.colors,
colors: SurfaceColors = SurfaceDefaults.colors(),
padding: ComponentPadding = SurfaceDefaults.padding,
content: @Composable BoxScope.() -> Unit
) {
@@ -100,25 +103,34 @@ private fun Modifier.surface(
* Defaults of surface.
*/
object SurfaceDefaults {
val colors: SurfaceColors
@Composable
@ReadOnlyComposable
get() = defaultSurfaceColors()
/**
* 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
fun colors(
contentColor: Color = SurfaceProperties.ContentColor.toColor(),
backgroundColor: Color = SurfaceProperties.BackgroundColor.toColor()
) = SurfaceColors(contentColor, backgroundColor)
/**
* Returns the default padding of surface.
* @return [ComponentPadding]
*/
val padding: ComponentPadding
@Composable
@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 }
@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.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
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.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.clickable
import com.highcapable.betterandroid.compose.extension.ui.componentState
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
import com.highcapable.flexiui.toShape
import kotlin.math.roundToInt
/**
* Colors defines for switch.
* @param thumbColor the color of thumb.
* @param trackInactive the color of track when switch is inactive.
* @param trackActive the color of track when switch is active.
* @see SwitchDefaults.colors
*/
@Immutable
data class SwitchColors(
val thumbColor: Color,
val trackInactive: Color,
val trackActive: Color
val thumbBorderColor: Color,
val trackBorderColor: Color,
val trackInactiveColor: Color,
val trackActiveColor: Color
)
/**
* Style defines for switch.
* @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 thumbBorder the border of thumb.
* @param trackBorder the border of track.
* @param trackWidth the width of track.
* @param trackHeight the height of track.
* @see SwitchDefaults.style
*/
@Immutable
data class SwitchStyle(
@@ -103,10 +95,10 @@ data class SwitchStyle(
val thumbShadowSize: Dp,
val thumbShape: Shape,
val trackShape: Shape,
val thumbBorder: BorderStroke,
val trackBorder: BorderStroke,
val trackWidth: Dp,
val trackHeight: Dp
val trackHeight: Dp,
val thumbBorderWidth: Dp,
val trackBorderWidth: Dp
)
/**
@@ -125,8 +117,8 @@ fun Switch(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
colors: SwitchColors = SwitchDefaults.colors,
style: SwitchStyle = SwitchDefaults.style,
colors: SwitchColors = SwitchDefaults.colors(),
style: SwitchStyle = SwitchDefaults.style(),
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable (RowScope.() -> Unit)? = null
@@ -142,12 +134,12 @@ fun Switch(
if (!hovered && !dragging) offsetX = if (checked) maxOffsetX else 0f
val animatedOffsetX by animateFloatAsState(offsetX)
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. */
fun updateTrackColor() {
val fraction = (offsetX / maxOffsetX).coerceIn(0f, 1f)
trackColor = lerp(colors.trackInactive, colors.trackActive, fraction)
trackColor = lerp(colors.trackInactiveColor, colors.trackActiveColor, fraction)
}
updateTrackColor()
val animatedTrackColor by animateColorAsState(trackColor)
@@ -166,7 +158,7 @@ fun Switch(
offsetX = if (checked) 0f else maxOffsetX
onCheckedChange(!checked)
}.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)
.padding(style.padding),
verticalAlignment = Alignment.CenterVertically,
@@ -183,7 +175,7 @@ fun Switch(
.scale(animatedScale)
.shadow(style.thumbShadowSize, style.thumbShape)
.background(colors.thumbColor, style.thumbShape)
.borderOrElse(style.thumbBorder, style.thumbShape)
.borderOrElse(style.thumbBorderWidth, colors.thumbBorderColor, style.thumbShape)
.draggable(
enabled = enabled,
orientation = Orientation.Horizontal,
@@ -232,49 +224,93 @@ fun Switch(
* Defaults of switch.
*/
object SwitchDefaults {
val colors: SwitchColors
@Composable
@ReadOnlyComposable
get() = defaultSwitchColors()
val style: SwitchStyle
@Composable
@ReadOnlyComposable
get() = defaultSwitchStyle()
/**
* 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
fun colors(
thumbColor: Color = SwitchProperties.ThumbColor,
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
fun style(
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
private fun defaultSwitchColors() = SwitchColors(
thumbColor = Color.White,
trackInactive = LocalColors.current.themeTertiary,
trackActive = LocalColors.current.themePrimary
)
@Composable
@ReadOnlyComposable
private fun defaultSwitchStyle() = SwitchStyle(
padding = ComponentPadding(horizontal = DefaultSwitchPadding),
contentSpacing = LocalSizes.current.spacingSecondary,
thumbRadius = DefaultThumbRadius,
thumbGain = DefaultThumbGain,
thumbShadowSize = DefaultThumbShadowSize,
thumbShape = LocalShapes.current.tertiary,
trackShape = LocalShapes.current.tertiary,
thumbBorder = defaultSwitchBorder(),
trackBorder = defaultSwitchBorder(),
trackWidth = DefaultTrackWidth,
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
/**
* Properties for [Switch].
*/
@Stable
internal object SwitchProperties {
val ThumbColor = Color.White
val ThumbBorderColor = ColorsDescriptor.TextPrimary
val TrackBorderColor = ColorsDescriptor.TextPrimary
val TrackInactiveColor = ColorsDescriptor.ThemeTertiary
val TrackActiveColor = ColorsDescriptor.ThemePrimary
val Padding = ComponentPadding(horizontal = 3.5.dp)
val ContentSpacing = SizesDescriptor.SpacingSecondary
val ThumbRadius = 6.5.dp
const val ThumbGain = 1.1f
val ThumbShadowSize = 0.5.dp
val ThumbShape = ShapesDescriptor.Tertiary
val TrackShape = ShapesDescriptor.Tertiary
val TrackWidth = 40.dp
val TrackHeight = 20.dp
val ThumbBorderWidth = SizesDescriptor.BorderSizeTertiary
val TrackBorderWidth = SizesDescriptor.BorderSizeTertiary
}

View File

@@ -33,7 +33,6 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
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.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.component.interaction.rippleClickable
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toShape
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Colors defines for tab.
* @param indicatorColor the indicator color.
* @param selectedContentColor the selected content color.
* @param unselectedContentColor the unselected content color.
* @see TabDefaults.colors
*/
@Immutable
data class TabColors(
@@ -95,11 +94,7 @@ data class TabColors(
/**
* Style defines for tab.
* @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.
* @see TabDefaults.style
*/
@Immutable
data class TabStyle(
@@ -125,8 +120,8 @@ data class TabStyle(
fun TabRow(
selectedTabIndex: Int = 0,
modifier: Modifier = Modifier,
colors: TabColors = TabDefaults.colors,
style: TabStyle = TabDefaults.style,
colors: TabColors = TabDefaults.colors(),
style: TabStyle = TabDefaults.style(),
indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) },
tabs: @Composable () -> Unit
) {
@@ -177,8 +172,8 @@ fun TabRow(
fun ScrollableTabRow(
selectedTabIndex: Int = 0,
modifier: Modifier = Modifier,
colors: TabColors = TabDefaults.colors,
style: TabStyle = TabDefaults.style,
colors: TabColors = TabDefaults.colors(),
style: TabStyle = TabDefaults.style(),
scrollState: ScrollState = rememberScrollState(),
indicator: @Composable TabRowScope.() -> Unit = { TabIndicator(modifier = Modifier.tabIndicatorOffset()) },
tabs: @Composable () -> Unit
@@ -249,18 +244,18 @@ fun Tab(
modifier: Modifier = Modifier,
selectedContentColor: Color = Color.Unspecified,
unselectedContentColor: Color = Color.Unspecified,
contentPadding: PaddingValues? = null,
contentPadding: ComponentPadding? = null,
contentShape: Shape? = null,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
val currentSelectedContentColor = selectedContentColor.orNull()
?: LocalTabSelectedContentColor.current.orNull() ?: TabDefaults.colors.selectedContentColor
?: LocalTabSelectedContentColor.current.orNull() ?: TabDefaults.colors().selectedContentColor
val currentUnselectedContentColor = unselectedContentColor.orNull()
?: LocalTabUnselectedContentColor.current.orNull() ?: TabDefaults.colors.unselectedContentColor
val currentContentPadding = contentPadding ?: LocalTabContentPadding.current ?: TabDefaults.style.contentPadding
val currentContentShape = contentShape ?: LocalTabContentShape.current ?: TabDefaults.style.contentShape
?: LocalTabUnselectedContentColor.current.orNull() ?: TabDefaults.colors().unselectedContentColor
val currentContentPadding = contentPadding ?: LocalTabContentPadding.current ?: TabDefaults.style().contentPadding
val currentContentShape = contentShape ?: LocalTabContentShape.current ?: TabDefaults.style().contentShape
val contentColor by animateColorAsState(if (selected) currentSelectedContentColor else currentUnselectedContentColor)
val contentIconStyle = LocalIconStyle.current.copy(tint = contentColor)
val contentTextStyle = LocalTextStyle.current.copy(color = contentColor)
@@ -508,45 +503,68 @@ private enum class TabSlots { Tabs, TabsAverage, Indicator }
* Defaults of tab.
*/
object TabDefaults {
val colors: TabColors
@Composable
@ReadOnlyComposable
get() = defaultTabColors()
val style: TabStyle
@Composable
@ReadOnlyComposable
get() = defaultTabStyle()
/**
* 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
fun colors(
indicatorColor: Color = TabProperties.IndicatorColor.toColor(),
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
fun style(
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 LocalTabUnselectedContentColor = compositionLocalOf { Color.Unspecified }
private val LocalTabContentPadding = compositionLocalOf<PaddingValues?> { null }
private val LocalTabContentPadding = compositionLocalOf<ComponentPadding?> { 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

View File

@@ -27,7 +27,7 @@ import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
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.style.TextOverflow
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.DefaultTypography
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.toColor
/**
* Flexi UI basic text.
@@ -110,7 +111,7 @@ fun Text(
onTextLayout: (TextLayoutResult) -> Unit = {}
) {
val currentStyle = style ?: LocalTextStyle.current
val currentColor = color.orNull() ?: currentStyle.color.orNull() ?: defaultTextColor()
val currentColor = color.orNull() ?: currentStyle.color.orNull() ?: TextProperties.Color.toColor()
BasicText(
text = text,
modifier = modifier,
@@ -124,9 +125,14 @@ fun Text(
)
}
@Stable
internal object TextProperties {
val Color = ColorsDescriptor.TextPrimary
}
/**
* CompositionLocal containing the preferred [TextStyle]
* that will be used by text by default.
* Composition local containing the preferred [TextStyle]
* that will be used by [Text] by default.
*/
val LocalTextStyle = compositionLocalOf { DefaultTextStyle }
@@ -144,8 +150,4 @@ fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
}
@Composable
@ReadOnlyComposable
internal fun defaultTextColor() = LocalColors.current.textPrimary
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.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
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.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
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.componentState
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.betterandroid.compose.extension.ui.solidColor
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.resources.FlexiIcons
import com.highcapable.flexiui.resources.icon.Backspace
import com.highcapable.flexiui.resources.icon.ViewerClose
import com.highcapable.flexiui.resources.icon.ViewerOpen
import com.highcapable.flexiui.toColor
import com.highcapable.flexiui.toDp
/**
* Colors defines for text field.
* @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.
* @see TextFieldDefaults.colors
*/
@Immutable
data class TextFieldColors(
@@ -124,8 +115,23 @@ data class TextFieldColors(
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.
* @see TextFieldDefaults.colors
* @param highlightContentColor the highlight content color, usually for text color.
* @param menuColors the dropdown menu colors.
*/
@@ -135,25 +141,6 @@ data class AutoCompleteBoxColors(
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.
* @param checkCase whether to check case, default is true.
@@ -202,8 +189,8 @@ fun TextField(
onValueChange: (TextFieldValue) -> Unit,
completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -228,24 +215,21 @@ fun TextField(
else -> colors.decorInactiveTint
})
val animatedBorderColor by animateColorAsState(when {
focused || hovered -> style.borderActive.solidColor
else -> style.borderInactive.solidColor
focused || hovered -> colors.borderActiveColor
else -> colors.borderInactiveColor
})
val animatedBorderWidth by animateDpAsState(when {
focused -> style.borderActive.width
else -> style.borderInactive.width
focused -> style.borderActiveWith
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
BoxWithConstraints(
modifier = Modifier.textField(
enabled = enabled,
colors = colors,
style = style,
border = border,
borderColor = animatedBorderColor,
borderWidth = animatedBorderWidth,
interactionSource = interactionSource,
then = modifier
).pointerHoverState(TextFieldPointerState.Text)
@@ -351,8 +335,8 @@ fun TextField(
onValueChange: (String) -> Unit,
completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -427,8 +411,8 @@ fun PasswordTextField(
onValueChange: (TextFieldValue) -> Unit,
defaultPasswordVisible: Boolean = false,
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@@ -475,7 +459,7 @@ fun PasswordTextField(
if (value.text.isEmpty() && animatedSize == 0.dp) passwordVisible = defaultPasswordVisible
IconToggleButton(
modifier = Modifier.size(animatedSize).pointerHoverState(TextFieldPointerState.Common),
style = IconButtonDefaults.style.copy(padding = TextDecorIconPadding),
style = IconButtonDefaults.style(padding = TextDecorIconPadding),
checked = passwordVisible,
onCheckedChange = {
passwordVisible = it
@@ -518,8 +502,8 @@ fun PasswordTextField(
onValueChange: (String) -> Unit,
defaultPasswordVisible: Boolean = false,
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@@ -589,8 +573,8 @@ fun BackspaceTextField(
onValueChange: (TextFieldValue) -> Unit,
completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -647,7 +631,7 @@ fun BackspaceTextField(
focusRequester.requestFocus()
},
modifier = Modifier.width(animatedSize).pointerHoverState(TextFieldPointerState.Common),
style = IconButtonDefaults.style.copy(padding = TextDecorIconPadding),
style = IconButtonDefaults.style(padding = TextDecorIconPadding),
enabled = enabled,
interactionSource = cInteractionSource
) { Icon(imageVector = FlexiIcons.Backspace) }
@@ -688,8 +672,8 @@ fun BackspaceTextField(
onValueChange: (String) -> Unit,
completionValues: List<String> = emptyList(),
modifier: Modifier = Modifier,
colors: TextFieldColors = TextFieldDefaults.colors,
style: TextFieldStyle = TextFieldDefaults.style,
colors: TextFieldColors = TextFieldDefaults.colors(),
style: TextFieldStyle = TextFieldDefaults.style(),
enabled: Boolean = true,
readOnly: Boolean = false,
completionOptions: AutoCompleteOptions = AutoCompleteOptions(),
@@ -883,9 +867,10 @@ private fun TextFieldDecorationBox(
@Composable
private fun TextFieldStyle(colors: TextFieldColors, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalTextSelectionColors provides colors.selectionColors) {
content()
}
CompositionLocalProvider(
LocalTextSelectionColors provides colors.selectionColors,
content = content
)
}
internal expect fun Modifier.pointerHoverState(state: TextFieldPointerState): Modifier
@@ -902,7 +887,8 @@ private fun Modifier.textField(
enabled: Boolean,
colors: TextFieldColors,
style: TextFieldStyle,
border: BorderStroke,
borderColor: Color,
borderWidth: Dp,
interactionSource: MutableInteractionSource,
then: Modifier
) = composed(
@@ -911,7 +897,8 @@ private fun Modifier.textField(
properties["enabled"] = enabled
properties["colors"] = colors
properties["style"] = style
properties["border"] = border
properties["borderColor"] = borderColor
properties["borderWidth"] = borderWidth
}
) {
componentState(enabled)
@@ -919,7 +906,7 @@ private fun Modifier.textField(
.hoverable(interactionSource, enabled)
.clip(style.shape)
.background(colors.backgroundColor, style.shape)
.borderOrElse(border, style.shape)
.borderOrElse(borderWidth, borderColor, style.shape)
.then(then)
}
@@ -940,55 +927,97 @@ private fun Modifier.textFieldPadding(
* Defaults of text field.
*/
object TextFieldDefaults {
val colors: TextFieldColors
@Composable
@ReadOnlyComposable
get() = defaultTextFieldColors()
val style: TextFieldStyle
@Composable
@ReadOnlyComposable
get() = defaultTextFieldStyle()
/**
* 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
fun colors(
textColor: Color = TextFieldProperties.TextColor.toColor(),
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
fun style(
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
@ReadOnlyComposable
private fun defaultTextFieldColors() = TextFieldColors(
textColor = defaultTextColor(),
cursorColor = LocalColors.current.themePrimary,
selectionColors = TextSelectionColors(
handleColor = LocalColors.current.themePrimary,
backgroundColor = LocalColors.current.themeSecondary
),
completionColors = AutoCompleteBoxColors(
highlightContentColor = LocalColors.current.themePrimary,
menuColors = DropdownMenuDefaults.colors
),
placeholderContentColor = LocalColors.current.textSecondary,
decorInactiveTint = LocalColors.current.themeSecondary,
decorActiveTint = LocalColors.current.themePrimary,
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)
@Stable
internal object TextFieldProperties {
val TextColor = TextProperties.Color
val CursorColor = ColorsDescriptor.ThemePrimary
val SelectionHandleColor = ColorsDescriptor.ThemePrimary
val SelectionBackgroundColor = ColorsDescriptor.ThemeSecondary
val CompletionHighlightContentColor = ColorsDescriptor.ThemePrimary
val PlaceholderContentColor = ColorsDescriptor.TextSecondary
val DecorInactiveTint = ColorsDescriptor.ThemeSecondary
val DecorActiveTint = ColorsDescriptor.ThemePrimary
val BorderInactiveColor = ColorsDescriptor.ThemeSecondary
val BorderActiveColor = ColorsDescriptor.ThemePrimary
val BackgroundColor = Color.Transparent
val Padding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
val BorderInactiveWith = SizesDescriptor.BorderSizeSecondary
val BorderActiveWith = SizesDescriptor.BorderSizePrimary
}
private val TextDecorIconSize = 24.dp
private val TextDecorIconPadding = ComponentPadding(2.dp)

View File

@@ -19,7 +19,7 @@
*
* This file is created by fankes on 2023/11/10.
*/
@file:Suppress("unused")
@file:Suppress("unused", "ConstPropertyName")
package com.highcapable.flexiui.component.interaction
@@ -27,7 +27,7 @@ import androidx.compose.foundation.Indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
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.selectable
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
/**
* Style defines for ripple.
* @param bounded whether the ripple is bounded.
* @param radius the radius.
* @param color the color.
* @see InteractionDefaults.rippleStyle
*/
@Immutable
data class RippleStyle(
@@ -62,7 +61,7 @@ data class RippleStyle(
* @return [Indication]
*/
@Composable
fun rememberRipple(style: RippleStyle = InteractionDefaults.rippleStyle) =
fun rememberRipple(style: RippleStyle = InteractionDefaults.rippleStyle()) =
materialRememberRipple(style.bounded, style.radius, style.color)
/**
@@ -95,7 +94,7 @@ fun Modifier.rippleClickable(
properties["onClick"] = onClick
}
) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle)
clickable(
onClick = onClick,
@@ -146,7 +145,7 @@ fun Modifier.rippleCombinedClickable(
properties["onClick"] = onClick
}
) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle)
combinedClickable(
onClick = onClick,
@@ -190,7 +189,7 @@ fun Modifier.rippleToggleable(
properties["onValueChange"] = onValueChange
}
) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle)
toggleable(
value = value,
@@ -231,7 +230,7 @@ fun Modifier.rippleSelectable(
properties["onClick"] = onClick
}
) {
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle
val currentRippleStyle = rippleStyle ?: InteractionDefaults.rippleStyle()
val currentIndication = rememberRipple(currentRippleStyle)
selectable(
selected = selected,
@@ -247,22 +246,35 @@ fun Modifier.rippleSelectable(
* Defaults of interaction.
*/
object InteractionDefaults {
val rippleStyle: RippleStyle
@Composable
@ReadOnlyComposable
get() = LocalRippleStyle.current ?: defaultRippleStyle()
/**
* 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
fun rippleStyle(
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.
*/
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.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.TextStyle
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.window.Dialog
import com.highcapable.betterandroid.compose.extension.ui.window.DialogPropertiesWrapper
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.LocalTypography
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.SizesDescriptor
import com.highcapable.flexiui.TypographyDescriptor
import com.highcapable.flexiui.component.AreaBox
import com.highcapable.flexiui.component.AreaBoxColors
import com.highcapable.flexiui.component.AreaBoxDefaults
import com.highcapable.flexiui.component.AreaBoxProperties
import com.highcapable.flexiui.component.AreaBoxStyle
import com.highcapable.flexiui.component.Button
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.platform.ActualPlatform
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.
* @param titleTextColor the title text color.
* @param titleIconTint the title icon tint.
* @param contentTextColor the content text color.
* @see FlexiDialogDefaults.colors
*/
@Immutable
data class FlexiDialogColors(
val backgroundColor: Color,
val borderColor: Color,
val titleTextColor: Color,
val titleIconTint: Color,
val contentTextColor: Color
@@ -85,26 +93,21 @@ data class FlexiDialogColors(
/**
* Style defines for flexi dialog.
* @param boxStyle the style of area box.
* @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.
* @see FlexiDialogDefaults.style
*/
@Immutable
data class FlexiDialogStyle(
val boxStyle: AreaBoxStyle,
val titleTextStyle: TextStyle,
val contentTextStyle: TextStyle,
val maxWidth: Dp,
val maxHeight: Dp,
val outPadding: ComponentPadding,
val padding: ComponentPadding,
val insetsPadding: ComponentPadding,
val titlePadding: ComponentPadding,
val contentPadding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val shadowSize: Dp,
val buttonsSpacing: Dp
)
@@ -130,8 +133,8 @@ fun FlexiDialog(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
animated: Boolean = true,
colors: FlexiDialogColors = FlexiDialogDefaults.colors,
style: FlexiDialogStyle = FlexiDialogDefaults.style,
colors: FlexiDialogColors = FlexiDialogDefaults.colors(),
style: FlexiDialogStyle = FlexiDialogDefaults.style(),
contentAlignment: Alignment = Alignment.TopStart,
properties: DialogPropertiesWrapper = DefaultDialogProperties,
title: (@Composable RowScope.() -> Unit)? = null,
@@ -185,8 +188,8 @@ fun FlexiDialog(
modifier: Modifier = Modifier,
animated: Boolean = true,
scrimAnimated: Boolean = false,
colors: FlexiDialogColors = FlexiDialogDefaults.colors,
style: FlexiDialogStyle = FlexiDialogDefaults.style,
colors: FlexiDialogColors = FlexiDialogDefaults.colors(),
style: FlexiDialogStyle = FlexiDialogDefaults.style(),
contentAlignment: Alignment = Alignment.TopStart,
properties: DialogPropertiesWrapper = DefaultDialogProperties,
title: (@Composable RowScope.() -> Unit)? = null,
@@ -248,10 +251,20 @@ fun FlexiDialog(
BasicFlexiDialog(
visible = visible,
onDismissRequest = onDismissRequest,
modifier = Modifier.padding(style.outPadding).then(modifier),
modifier = modifier,
animated = animated,
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,
maxHeight = style.maxHeight,
contentAlignment = contentAlignment,
@@ -276,7 +289,9 @@ private fun BasicFlexiDialog(
modifier: Modifier,
animated: Boolean,
scrimAnimated: Boolean,
boxColors: AreaBoxColors,
boxStyle: AreaBoxStyle,
insetsPadding: ComponentPadding,
maxWidth: Dp,
maxHeight: Dp,
contentAlignment: Alignment,
@@ -305,17 +320,17 @@ private fun BasicFlexiDialog(
properties = propertiesCopy,
onDismissRequest = onDismissRequest
) {
Box(
modifier = if (animated)
Modifier.graphicsLayer {
scaleX = animatedScale
scaleY = animatedScale
alpha = animatedAlpha
}.then(modifier)
else Modifier.then(modifier)
) {
val sModifier = if (animated)
Modifier.graphicsLayer {
scaleX = animatedScale
scaleY = animatedScale
alpha = animatedAlpha
}.then(modifier)
else modifier
Box(modifier = Modifier.padding(insetsPadding).then(sModifier)) {
AreaBox(
modifier = Modifier.widthIn(max = maxWidth).heightIn(max = maxHeight),
colors = boxColors,
style = boxStyle,
contentAlignment = contentAlignment
) { content() }
@@ -327,45 +342,101 @@ private fun BasicFlexiDialog(
* Defaults of flexi dialog.
*/
object FlexiDialogDefaults {
val colors: FlexiDialogColors
@Composable
@ReadOnlyComposable
get() = defaultFlexiDialogColors()
val style: FlexiDialogStyle
@Composable
@ReadOnlyComposable
get() = defaultFlexiDialogStyle()
/**
* 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
fun colors(
backgroundColor: Color = FlexiDialogProperties.BackgroundColor.toColor(),
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
fun style(
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
@ReadOnlyComposable
private fun defaultFlexiDialogColors() = FlexiDialogColors(
titleTextColor = LocalColors.current.textPrimary,
titleIconTint = LocalColors.current.textPrimary,
contentTextColor = LocalColors.current.textSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultFlexiDialogStyle() = FlexiDialogStyle(
boxStyle = AreaBoxDefaults.style.copy(padding = ComponentPadding(LocalSizes.current.spacingSecondary)),
titleTextStyle = LocalTypography.current.titleSecondary,
contentTextStyle = LocalTypography.current.primary,
maxWidth = DefaultMaxWidth,
maxHeight = DefaultMaxHeight,
outPadding = ComponentPadding(horizontal = DefaultHorizontalOutPadding),
titlePadding = ComponentPadding(LocalSizes.current.spacingSecondary),
contentPadding = ComponentPadding(LocalSizes.current.spacingSecondary),
buttonsSpacing = LocalSizes.current.spacingSecondary
)
@Stable
internal object FlexiDialogProperties {
val BackgroundColor = AreaBoxProperties.BackgroundColor
val BorderColor = AreaBoxProperties.BorderColor
val TitleTextColor = ColorsDescriptor.TextPrimary
val TitleIconTint = ColorsDescriptor.TextPrimary
val ContentTextColor = ColorsDescriptor.TextSecondary
val TitleTextStyle = TypographyDescriptor.TitleSecondary
val ContentTextStyle = TypographyDescriptor.Primary
val MaxWidth = 300.dp
val MaxHeight = 500.dp
val Padding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
val InsetsPadding = ComponentPadding(horizontal = 50.dp)
val TitlePadding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
val ContentPadding = PaddingDescriptor(SizesDescriptor.SpacingSecondary)
val Shape = AreaBoxProperties.Shape
val BorderWidth = AreaBoxProperties.BorderWidth
val ShadowSize = AreaBoxProperties.ShadowSize
val ButtonsSpacing = SizesDescriptor.SpacingSecondary
}
private const val AnimationDuration = 250
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 val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity)
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.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.highcapable.flexiui.component.DesktopContextMenuDefaults
import com.highcapable.flexiui.component.DesktopContextMenuRepresentation
import com.highcapable.flexiui.component.ContextMenuDefaults
import com.highcapable.flexiui.component.FlexiContextMenuRepresentation
import com.highcapable.flexiui.component.LocalContextMenuColors
import com.highcapable.flexiui.component.LocalContextMenuStyle
import com.highcapable.flexiui.component.defaultContextMenuColors
import com.highcapable.flexiui.component.defaultContextMenuStyle
@Composable
internal actual fun FlexiThemeContent(content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalContextMenuColors provides defaultContextMenuColors(),
LocalContextMenuStyle provides defaultContextMenuStyle()
LocalContextMenuColors provides ContextMenuDefaults.colors(),
LocalContextMenuStyle provides ContextMenuDefaults.style(),
) {
CompositionLocalProvider(
LocalContextMenuRepresentation provides
DesktopContextMenuRepresentation(
colors = DesktopContextMenuDefaults.colors,
style = DesktopContextMenuDefaults.style
),
LocalContextMenuRepresentation provides FlexiContextMenuRepresentation(),
content = content
)
}

View File

@@ -35,7 +35,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
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.FocusManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
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.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
import com.highcapable.betterandroid.compose.extension.ui.orNull
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.ColorsDescriptor
import com.highcapable.flexiui.PaddingDescriptor
import com.highcapable.flexiui.ShapesDescriptor
import com.highcapable.flexiui.SizesDescriptor
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
/**
* Colors defines for the context menu.
* @param contentColor the content color, usually for the text color.
* @param borderColor the border color.
* @see ContextMenuDefaults.colors
*/
@Immutable
data class ContextMenuColors(
@@ -81,26 +85,26 @@ data class ContextMenuColors(
/**
* Style defines for the context menu.
* @param contentStyle the content style of area box.
* @param borderStyle the border style of area box.
* @see ContextMenuDefaults.style
*/
@Immutable
data class ContextMenuStyle(
val contentStyle: AreaBoxStyle?,
val borderStyle: AreaBoxStyle?
val padding: ComponentPadding,
val shape: Shape,
val borderWidth: Dp,
val contentPadding: ComponentPadding,
val contentShape: Shape,
val shadowSize: Dp
)
internal class DesktopContextMenuRepresentation(
private val colors: ContextMenuColors,
private val style: ContextMenuStyle
) : ContextMenuRepresentation {
internal class FlexiContextMenuRepresentation : ContextMenuRepresentation {
@Composable
override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) {
val colors = LocalContextMenuColors.current ?: ContextMenuDefaults.colors()
val style = LocalContextMenuStyle.current ?: ContextMenuDefaults.style()
val status = state.status
if (status is ContextMenuState.Status.Open) {
val contentStyle = style.contentStyle ?: return
val borderStyle = style.borderStyle ?: return
var focusManager: FocusManager? by mutableStateOf(null)
var inputModeManager: InputModeManager? by mutableStateOf(null)
Popup(
@@ -129,12 +133,20 @@ internal class DesktopContextMenuRepresentation(
modifier = Modifier
.width(IntrinsicSize.Max)
.verticalScroll(rememberScrollState()),
color = colors.borderColor,
style = borderStyle
colors = AreaBoxDefaults.colors(backgroundColor = colors.borderColor),
style = AreaBoxDefaults.style(
padding = style.padding,
shape = style.shape,
borderWidth = style.borderWidth,
shadowSize = style.shadowSize
)
) {
items().forEach { item ->
MenuItemContent(
style = contentStyle,
style = AreaBoxDefaults.style(
padding = style.contentPadding,
shape = style.contentShape
),
onClick = {
state.status = ContextMenuState.Status.Closed
item.onClick()
@@ -164,7 +176,7 @@ private fun MenuItemContent(
maxWidth = MenuContentMaxWidth,
minHeight = MenuContentMinHeight
),
color = Color.Transparent,
colors = AreaBoxDefaults.colors(backgroundColor = Color.Transparent),
style = style,
verticalAlignment = Alignment.CenterVertically
) { content() }
@@ -185,61 +197,74 @@ private fun Modifier.onHover(onHover: (Boolean) -> Unit) = pointerInput(Unit) {
/**
* Defaults of context menu.
*/
object DesktopContextMenuDefaults {
val colors: ContextMenuColors
@Composable
@ReadOnlyComposable
get() = LocalContextMenuColors.current
val style: ContextMenuStyle
@Composable
@ReadOnlyComposable
get() = LocalContextMenuStyle.current
object ContextMenuDefaults {
/**
* 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
fun colors(
contentColor: Color = ContextMenuProperties.ContentColor.toColor(),
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
fun style(
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]
* that will be used by context menu by default.
* Composition local containing the preferred [ContextMenuColors]
* that will be used by [ContextMenuRepresentation] by default.
*/
val LocalContextMenuColors = compositionLocalOf {
ContextMenuColors(
borderColor = Color.Unspecified,
contentColor = Color.Unspecified
)
}
val LocalContextMenuColors = compositionLocalOf<ContextMenuColors?> { null }
/**
* CompositionLocal containing the preferred [ContextMenuStyle]
* that will be used by context menu by default.
* Composition local containing the preferred [ContextMenuStyle]
* that will be used by [ContextMenuRepresentation] by default.
*/
val LocalContextMenuStyle = compositionLocalOf {
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
val LocalContextMenuStyle = compositionLocalOf<ContextMenuStyle?> { null }
private val MenuContentMinWidth = 112.dp
private val MenuContentMaxWidth = 280.dp