From f0ec24f5dfd593d600fdcaf05f696ace93e71f36 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Wed, 10 Jan 2024 02:45:09 +0800 Subject: [PATCH] refactor: heavy changes, use new norm rebuild components --- .../kotlin/com/highcapable/flexiui/Colors.kt | 47 ++- .../com/highcapable/flexiui/FlexiTheme.kt | 20 ++ .../kotlin/com/highcapable/flexiui/Shapes.kt | 37 +- .../kotlin/com/highcapable/flexiui/Sizes.kt | 113 ++++++ .../com/highcapable/flexiui/Typography.kt | 40 ++- .../highcapable/flexiui/component/AppBar.kt | 202 ++++++----- .../highcapable/flexiui/component/AreaBox.kt | 169 +++++---- .../highcapable/flexiui/component/Button.kt | 276 +++++++------- .../highcapable/flexiui/component/CheckBox.kt | 140 ++++---- .../highcapable/flexiui/component/Dropdown.kt | 339 +++++++++++------- .../com/highcapable/flexiui/component/Icon.kt | 69 ++-- .../highcapable/flexiui/component/ItemBox.kt | 152 +++++--- .../flexiui/component/NavigationBar.kt | 174 ++++++--- .../flexiui/component/ProgressIndicator.kt | 332 +++++++++-------- .../flexiui/component/RadioButton.kt | 143 ++++---- .../highcapable/flexiui/component/Scaffold.kt | 2 +- .../highcapable/flexiui/component/Slider.kt | 194 ++++++---- .../highcapable/flexiui/component/Surface.kt | 52 +-- .../highcapable/flexiui/component/Switch.kt | 186 ++++++---- .../com/highcapable/flexiui/component/Tab.kt | 130 ++++--- .../com/highcapable/flexiui/component/Text.kt | 20 +- .../flexiui/component/TextField.kt | 249 +++++++------ .../component/interaction/Interaction.kt | 62 ++-- .../flexiui/component/window/Dialog.kt | 203 +++++++---- .../highcapable/flexiui/FlexiTheme.desktop.kt | 16 +- .../{DesktopContextMenu.kt => ContextMenu.kt} | 163 +++++---- 26 files changed, 2200 insertions(+), 1330 deletions(-) rename flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/{DesktopContextMenu.kt => ContextMenu.kt} (60%) diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Colors.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Colors.kt index 4850f0b..0de59a3 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Colors.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Colors.kt @@ -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), @@ -419,4 +437,31 @@ fun blueColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when else -> BlueLightColors } -internal val LocalColors = staticCompositionLocalOf { DefaultLightColors } \ No newline at end of file +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) \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/FlexiTheme.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/FlexiTheme.kt index 71e269c..6c3cd2d 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/FlexiTheme.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/FlexiTheme.kt @@ -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 diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Shapes.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Shapes.kt index 44564e7..4f6b550 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Shapes.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Shapes.kt @@ -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,10 +44,41 @@ 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( primary = RoundedCornerShape(15.dp), secondary = RoundedCornerShape(10.dp), tertiary = CircleShape -) \ No newline at end of file +) + +/** + * 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) \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Sizes.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Sizes.kt index fa46d4e..6f6677c 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Sizes.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Sizes.kt @@ -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. diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Typography.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Typography.kt index 5814653..6718ca5 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Typography.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/Typography.kt @@ -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 @@ -69,4 +84,27 @@ internal val DefaultTypography = Typography( fontSize = 12.sp, lineHeight = DefaultLineHeight ) -) \ No newline at end of file +) + +/** + * 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) \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AppBar.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AppBar.kt index a094d7b..80cd520 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AppBar.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AppBar.kt @@ -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 \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AreaBox.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AreaBox.kt index 4957445..637f2a5 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AreaBox.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/AreaBox.kt @@ -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 \ No newline at end of file +internal val LocalAreaBoxShape = compositionLocalOf { null } \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Button.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Button.kt index 066c585..d27af20 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Button.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Button.kt @@ -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 + ) } -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() +@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 } -@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() +@Stable +internal object IconButtonProperties { + val ContentColor = ColorsDescriptor.ThemePrimary + val BackgroundColor = Color.Transparent + const val RippleBounded = false + val Padding = ComponentPadding() + val Shape = ShapesDescriptor.Tertiary } -@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) \ No newline at end of file +internal val LocalPrimaryButton = compositionLocalOf { false } \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/CheckBox.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/CheckBox.kt index 3df5771..4a48645 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/CheckBox.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/CheckBox.kt @@ -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 \ No newline at end of file +@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 +} \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Dropdown.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Dropdown.kt index 9b6dec4..24df61e 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Dropdown.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Dropdown.kt @@ -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 { 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 { null } +private val LocalDropdownMenuContentShape = compositionLocalOf { null } private val MenuItemMinWidth = 112.dp private val MenuItemMaxWidth = 280.dp diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Icon.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Icon.kt index 4950eac..bd0a802 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Icon.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Icon.kt @@ -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() \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ItemBox.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ItemBox.kt index a50631d..58b7d95 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ItemBox.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ItemBox.kt @@ -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 diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/NavigationBar.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/NavigationBar.kt index 9f40e0c..d39974f 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/NavigationBar.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/NavigationBar.kt @@ -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 { null } private val LocalNavigationBarContentSpacing = compositionLocalOf { Dp.Unspecified } - -private val LocalNavigationBarContentPadding = compositionLocalOf { null } - +private val LocalNavigationBarContentPadding = compositionLocalOf { null } private val LocalNavigationBarContentShape = compositionLocalOf { 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 \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ProgressIndicator.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ProgressIndicator.kt index 5e1def6..a084da4 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ProgressIndicator.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/ProgressIndicator.kt @@ -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 { null } +internal val LocalProgressIndicatorStyle = compositionLocalOf { 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 @@ -537,4 +547,24 @@ private val FirstLineHeadEasing = CubicBezierEasing(0.2f, 0f, 0.8f, 1f) 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) \ No newline at end of file +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 +) \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt index 5d5c6ed..3dbeb3d 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/RadioButton.kt @@ -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 \ No newline at end of file +@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 +} \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Scaffold.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Scaffold.kt index 9cf50a7..5216c4b 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Scaffold.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Scaffold.kt @@ -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, diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Slider.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Slider.kt index 33a3f2b..2d57507 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Slider.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Slider.kt @@ -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 \ No newline at end of file +@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 +} \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Surface.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Surface.kt index e5e8d4e..7fe5b11 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Surface.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Surface.kt @@ -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() } -internal val LocalInSurface = compositionLocalOf { false } +@Stable +internal object SurfaceProperties { + val ContentColor = ColorsDescriptor.TextPrimary + val BackgroundColor = ColorsDescriptor.BackgroundPrimary + val Padding = PaddingDescriptor(SizesDescriptor.SpacingPrimary) +} -@Composable -@ReadOnlyComposable -private fun defaultSurfaceColors() = SurfaceColors( - contentColor = LocalColors.current.textPrimary, - backgroundColor = LocalColors.current.backgroundPrimary -) - -@Composable -@ReadOnlyComposable -private fun defaultSurfacePadding() = ComponentPadding(LocalSizes.current.spacingPrimary) \ No newline at end of file +internal val LocalInSurface = compositionLocalOf { false } \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Switch.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Switch.kt index 54b95f0..26a195c 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Switch.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Switch.kt @@ -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 \ No newline at end of file +/** + * 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 +} \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Tab.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Tab.kt index 37d4eaa..f766fd8 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Tab.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Tab.kt @@ -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 { null } - +private val LocalTabContentPadding = compositionLocalOf { null } private val LocalTabContentShape = compositionLocalOf { 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 \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Text.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Text.kt index 48589b2..ca710cf 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Text.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/Text.kt @@ -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 \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/TextField.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/TextField.kt index 3f027aa..e0c9b03 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/TextField.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/TextField.kt @@ -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 = 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 = 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 = 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 = 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) \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/interaction/Interaction.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/interaction/Interaction.kt index 22b375b..a92b536 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/interaction/Interaction.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/interaction/Interaction.kt @@ -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 { null } - -@Composable -@ReadOnlyComposable -private fun defaultRippleStyle() = RippleStyle( - bounded = true, - radius = Dp.Unspecified, - color = LocalColors.current.themeSecondary -) \ No newline at end of file +val LocalRippleStyle = compositionLocalOf { null } \ No newline at end of file diff --git a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/window/Dialog.kt b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/window/Dialog.kt index 40a76a8..8f8c42c 100644 --- a/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/window/Dialog.kt +++ b/flexiui-core/src/commonMain/kotlin/com/highcapable/flexiui/component/window/Dialog.kt @@ -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) diff --git a/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/FlexiTheme.desktop.kt b/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/FlexiTheme.desktop.kt index 87b33dc..4210b77 100644 --- a/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/FlexiTheme.desktop.kt +++ b/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/FlexiTheme.desktop.kt @@ -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 ) } diff --git a/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/DesktopContextMenu.kt b/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/ContextMenu.kt similarity index 60% rename from flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/DesktopContextMenu.kt rename to flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/ContextMenu.kt index a831b7d..0772bb0 100644 --- a/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/DesktopContextMenu.kt +++ b/flexiui-core/src/desktopMain/kotlin/com/highcapable/flexiui/component/ContextMenu.kt @@ -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) { + 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 { 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 { null } private val MenuContentMinWidth = 112.dp private val MenuContentMaxWidth = 280.dp