diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/BreadcrumbBar.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/BreadcrumbBar.kt index a9a56bff..5756d326 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/BreadcrumbBar.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/BreadcrumbBar.kt @@ -13,15 +13,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.konyaco.fluent.FluentTheme import com.konyaco.fluent.LocalContentAlpha import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.LocalTextStyle -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.ChevronRight -import com.konyaco.fluent.icons.filled.MoreHorizontal import com.konyaco.fluent.layout.overflow.OverflowActionScope import com.konyaco.fluent.layout.overflow.OverflowFlyoutContainer import com.konyaco.fluent.layout.overflow.OverflowPosition @@ -140,10 +136,9 @@ object BreadcrumbBarDefaults { enabled = enabled ) { FontIcon( - glyph = '\uE712', - vector = Icons.Filled.MoreHorizontal, + type = FontIconPrimitive.More, contentDescription = null, - iconSize = FontIconDefaults.fontSizeSmall + size = FontIconSize.Small ) } } @@ -164,10 +159,9 @@ object BreadcrumbBarDefaults { enabled = enabled ) { FontIcon( - glyph = '\uE712', - vector = Icons.Filled.MoreHorizontal, + type = FontIconPrimitive.More, contentDescription = null, - iconSize = FontIconDefaults.fontSizeStandard + size = FontIconSize.Standard ) } } @@ -193,7 +187,7 @@ fun BreadcrumbBarItem( colorScheme = colorScheme, chevronColors = chevronColors, textStyle = FluentTheme.typography.body, - chevronSize = FontIconDefaults.fontSizeSmall, + chevronSize = FontIconSize.Small, modifier = modifier, chevronVisible = chevronVisible, enabled = enabled, @@ -221,7 +215,7 @@ fun LargeBreadcrumbBarItem( colorScheme = colorScheme, chevronColors = chevronColors, textStyle = FluentTheme.typography.title, - chevronSize = FontIconDefaults.fontSizeStandard, + chevronSize = FontIconSize.Standard, modifier = modifier, chevronVisible = chevronVisible, enabled = enabled, @@ -254,7 +248,7 @@ private fun BasicBreadcrumbBarItem( colorScheme: VisualStateScheme, chevronColors: VisualStateScheme, textStyle: TextStyle, - chevronSize: TextUnit, + chevronSize: FontIconSize, modifier: Modifier = Modifier, chevronVisible: Boolean = true, enabled: Boolean = true, @@ -294,11 +288,10 @@ private fun BasicBreadcrumbBarItem( LocalTextStyle provides LocalTextStyle.current.copy(chevronColor) ) { FontIcon( - glyph = '\uE974', - vector = Icons.Filled.ChevronRight, + type = FontIconPrimitive.ChevronRight, contentDescription = null, modifier = Modifier.padding(horizontal = 4.dp), - iconSize = chevronSize + size = chevronSize ) } } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Button.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Button.kt index 0d825368..c54ac198 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Button.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Button.kt @@ -53,8 +53,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ChevronDown import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualState import com.konyaco.fluent.scheme.VisualStateScheme @@ -716,10 +714,11 @@ private fun AnimatedDropDownIcon(interaction: MutableInteractionSource) { targetValue = if (isPressed) 2.dp else 0.dp, animationSpec = tween(FluentDuration.ShortDuration, easing = FluentEasing.FastInvokeEasing) ) - Icon( - imageVector = Icons.Default.ChevronDown, + FontIcon( + type = FontIconPrimitive.ChevronDown, + size = FontIconSize.Small, contentDescription = null, - modifier = Modifier.graphicsLayer { translationY = animatedOffset.value.toPx() }.size(12.dp) + modifier = Modifier.graphicsLayer { translationY = animatedOffset.value.toPx() } ) } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarDatePicker.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarDatePicker.kt index ac9e4bd0..d91e15c1 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarDatePicker.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarDatePicker.kt @@ -7,8 +7,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.konyaco.fluent.ExperimentalFluentApi -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.CalendarLtr /** * A calendar view lets a user view and interact with a calendar that they can navigate by month, year, or decade. A user can select a single date or a range of dates. It doesn't have a picker surface and the calendar is always visible. @@ -50,7 +48,7 @@ fun CalendarDatePicker( "${day.year}/${day.monthValue + 1}/${day.day}" } ?: "Pick a date" ) - Icon(Icons.Default.CalendarLtr, null) + FontIcon(type = FontIconPrimitive.Calendar, contentDescription = null) } ) } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarView.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarView.kt index f8910378..6054191c 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarView.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CalendarView.kt @@ -52,9 +52,6 @@ import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer import com.konyaco.fluent.component.CalendarDatePickerState.ChooseType -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.CaretDown -import com.konyaco.fluent.icons.filled.CaretUp import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.collectVisualState import kotlinx.datetime.Clock @@ -375,16 +372,17 @@ private fun PaginationButton( ) { Box(Modifier.requiredSize(40.dp), Alignment.Center) { SubtleButton(modifier = Modifier.height(30.dp), onClick = onClick, iconOnly = true) { - if (up) Icon( - modifier = Modifier.size(12.dp), - imageVector = Icons.Filled.CaretUp, - contentDescription = "Up" - ) - else Icon( - modifier = Modifier.size(12.dp), - imageVector = Icons.Filled.CaretDown, - contentDescription = "Down" - ) + if (up) { + FontIconSolid8( + type = FontIconPrimitive.CaretUp, + contentDescription = "Up", + ) + } else { + FontIconSolid8( + type = FontIconPrimitive.CaretDown, + contentDescription = "Down", + ) + } } } } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CheckBox.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CheckBox.kt index 5ec4fef1..2cbbfff9 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CheckBox.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CheckBox.kt @@ -30,8 +30,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.Checkmark import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualStateScheme import com.konyaco.fluent.scheme.collectVisualState @@ -84,10 +82,10 @@ fun CheckBox( ) ) { Box(Modifier.fillMaxSize(), Alignment.Center) { - Icon( - modifier = Modifier.size(16.dp), - imageVector = Icons.Default.Checkmark, - contentDescription = null + FontIcon( + type = FontIconPrimitive.Accept, + contentDescription = null, + size = FontIconSize.Small ) } } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ColorPicker.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ColorPicker.kt index 31bd89b5..55000ad3 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ColorPicker.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ColorPicker.kt @@ -50,9 +50,6 @@ import com.konyaco.fluent.FluentTheme import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ChevronDown -import com.konyaco.fluent.icons.regular.ChevronUp import kotlinx.coroutines.flow.collectLatest import kotlin.math.PI import kotlin.math.atan2 @@ -187,10 +184,15 @@ fun ColorPicker( verticalAlignment = Alignment.CenterVertically ) { Text(text = text) - Icon( - imageVector = if (!expanded) Icons.Default.ChevronDown else Icons.Default.ChevronUp, + FontIcon( + type = if (!expanded) { + FontIconPrimitive.ChevronDown + } else { + FontIconPrimitive.ChevronUp + + }, contentDescription = text, - modifier = Modifier.size(12.dp) + size = FontIconSize.Small ) } }, diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CommandBar.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CommandBar.kt index 1ca4c27e..3bbac401 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CommandBar.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/CommandBar.kt @@ -33,8 +33,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.MoreHorizontal import com.konyaco.fluent.layout.alignLast import com.konyaco.fluent.layout.overflow.OverflowActionScope import com.konyaco.fluent.layout.overflow.OverflowRow @@ -229,11 +227,7 @@ internal fun CommandBarMoreButton(isLarge: Boolean, onClick: () -> Unit) { onClick = onClick, iconOnly = true, content = { - FontIcon( - glyph = '\uE712', - vector = Icons.Filled.MoreHorizontal, - contentDescription = null, - ) + FontIcon(type = FontIconPrimitive.More, contentDescription = null) }, modifier = if (isLarge) { Modifier.sizeIn( diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Expander.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Expander.kt index c6b57a73..7fd7d7e9 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Expander.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Expander.kt @@ -37,8 +37,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ChevronDown import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualStateScheme import com.konyaco.fluent.scheme.collectVisualState @@ -81,8 +79,8 @@ fun Expander( onClick = { onExpandedChanged(!expanded) }, content = { val degrees by animateFloatAsState(if (expanded) 180f else 0f) - Icon( - imageVector = Icons.Default.ChevronDown, + FontIcon( + type = FontIconPrimitive.ChevronDown, contentDescription = null, modifier = Modifier.graphicsLayer { rotationZ = degrees } ) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/FontIcon.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/FontIcon.kt index d6a05ef2..99281052 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/FontIcon.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/FontIcon.kt @@ -1,24 +1,71 @@ package com.konyaco.fluent.component +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.konyaco.fluent.LocalContentAlpha +import com.konyaco.fluent.LocalContentColor +import com.konyaco.fluent.animation.FluentDuration +import com.konyaco.fluent.animation.FluentEasing +import com.konyaco.fluent.icons.Icons +import com.konyaco.fluent.icons.filled.CaretDown +import com.konyaco.fluent.icons.filled.CaretLeft +import com.konyaco.fluent.icons.filled.CaretRight +import com.konyaco.fluent.icons.filled.CaretUp +import com.konyaco.fluent.icons.filled.Checkmark +import com.konyaco.fluent.icons.filled.ChevronRight +import com.konyaco.fluent.icons.filled.MoreHorizontal +import com.konyaco.fluent.icons.filled.Star +import com.konyaco.fluent.icons.regular.Add +import com.konyaco.fluent.icons.regular.ArrowLeft +import com.konyaco.fluent.icons.regular.ArrowRight +import com.konyaco.fluent.icons.regular.CalendarLtr +import com.konyaco.fluent.icons.regular.ChevronDown +import com.konyaco.fluent.icons.regular.ChevronUp +import com.konyaco.fluent.icons.regular.ClipboardPaste +import com.konyaco.fluent.icons.regular.Copy +import com.konyaco.fluent.icons.regular.Cut +import com.konyaco.fluent.icons.regular.Dismiss +import com.konyaco.fluent.icons.regular.Eye +import com.konyaco.fluent.icons.regular.Navigation +import com.konyaco.fluent.icons.regular.Search +import com.konyaco.fluent.icons.regular.Settings +import com.konyaco.fluent.icons.regular.Star +import com.konyaco.fluent.icons.regular.Subtract +import kotlin.jvm.JvmInline //TODO Public @Composable internal fun FontIcon( glyph: Char, modifier: Modifier = Modifier, - iconSize: TextUnit = FontIconDefaults.fontSizeStandard, + iconSize: TextUnit = FontIconSize.Standard.value.sp, fallback: (@Composable () -> Unit)? = null, ) { if (LocalFontIconFontFamily.current != null || fallback == null) { @@ -26,6 +73,7 @@ internal fun FontIcon( text = glyph.toString(), fontFamily = LocalFontIconFontFamily.current, fontSize = iconSize, + color = LocalContentColor.current.copy(LocalContentAlpha.current), modifier = Modifier.then(modifier) .height(with(LocalDensity.current) { iconSize.toDp() }), onTextLayout = { @@ -39,33 +87,233 @@ internal fun FontIcon( @Composable internal fun FontIcon( glyph: Char, - vector: ImageVector?, + vector: (() -> ImageVector)?, contentDescription: String?, modifier: Modifier = Modifier, - iconSize: TextUnit = FontIconDefaults.fontSizeStandard, - vectorSize: Dp = when(iconSize) { - FontIconDefaults.fontSizeStandard -> FontIconDefaults.vectorSizeStandard - FontIconDefaults.fontSizeSmall -> FontIconDefaults.vectorSizeSmall - else -> with(LocalDensity.current) { iconSize.toDp() } - } + iconSize: FontIconSize = FontIconSize.Standard, + fallbackSize: FontIconSize = iconSize ) { FontIcon( glyph = glyph, modifier = modifier, - iconSize = iconSize, + iconSize = iconSize.value.sp, fallback = if (vector == null) { null } else { - { Icon(vector, contentDescription, modifier = modifier.size(vectorSize)) } + { + Icon( + imageVector = vector(), + contentDescription = contentDescription, + modifier = modifier + .layout { measurable, constraints -> + val size = fallbackSize.value.dp.roundToPx() + val fontSize = iconSize.value.sp.roundToPx() + val placeable = measurable.measure(Constraints.fixed(size, size)) + layout(fontSize, fontSize) { + val offset = Alignment.Center.align( + IntSize(size, size), + IntSize(fontSize, fontSize), + layoutDirection + ) + placeable.place(offset) + } + } + ) + } } ) } -internal object FontIconDefaults { - val fontSizeStandard = 16.sp - val fontSizeSmall = 12.sp - val vectorSizeStandard = 16.dp - val vectorSizeSmall = 12.dp +//Font icon set that contains all icon from components. +enum class FontIconPrimitive( + internal val glyph: Char, + //Lazy create fallback icon + internal val vector: () -> ImageVector +) { + Accept('\uF78C', { Icons.Filled.Checkmark }), + Add('\uE710', { Icons.Default.Add }), + ArrowRight('\uE64D', { Icons.Default.ArrowRight }), + Cancel('\uE711', { Icons.Default.Dismiss }), + Calendar('\uE787', { Icons.Default.CalendarLtr }), + //Solid 8 + CaretDown('\uEDDC', { Icons.Filled.CaretDown }), + //Solid 8 + CaretLeft('\uEDD9', { Icons.Filled.CaretLeft }), + //Solid 8 + CaretRight('\uEDDA', { Icons.Filled.CaretRight }), + //Solid 8 + CaretUp('\uEDDB', { Icons.Filled.CaretUp }), + ChevronDown('\uE972', { Icons.Default.ChevronDown }), + ChevronRight('\uE974', { Icons.Filled.ChevronRight }), + ChevronUp('\uE70E', { Icons.Default.ChevronUp }), + ChromeBack('\uE830', { Icons.Default.ArrowLeft }), + Close('\uE624', { Icons.Default.Dismiss }), + Copy('\uE8C8', { Icons.Default.Copy }), + Cut('\uE8C6', { Icons.Default.Cut }), + Dash('\uE629', { Icons.Default.Subtract }), + FavoriteStarFull('\uE735', { Icons.Filled.Star }), + GlobalNavigation('\uE700', { Icons.Default.Navigation }), + More('\uE712', { Icons.Filled.MoreHorizontal }), + Paste('\uE77F', { Icons.Default.ClipboardPaste }), + RatingStar('\uE734', { Icons.Default.Star }), + RevealPassword('\uF78D', { Icons.Default.Eye }), + Search('\uF78B', { Icons.Default.Search }), + Settings('\uE713', { Icons.Default.Settings }) +} + +@Composable +fun FontIcon( + type: FontIconPrimitive, + contentDescription: String?, + modifier: Modifier = Modifier, + size: FontIconSize = FontIconSize.Standard, + fallbackSize: FontIconSize = FontIconSize(size.value + 2f) +) { + FontIcon( + glyph = type.glyph, + vector = type.vector, + contentDescription = contentDescription, + iconSize = size, + fallbackSize = fallbackSize, + modifier = modifier + ) +} + + +@Composable +fun FontIconSolid8( + type: FontIconPrimitive, + contentDescription: String?, + modifier: Modifier = Modifier, + size: FontIconSize = FontIconSize(8f), + fallbackSize: FontIconSize = FontIconSize(size.value + 6f) +) { + FontIcon( + type = type, + contentDescription = contentDescription, + modifier = modifier, + size = size, + fallbackSize = fallbackSize + ) +} + +object FontIconDefaults { + + @Composable + fun BackIcon( + interactionSource: InteractionSource, + size: FontIconSize = FontIconSize.Standard, + contentDescription: String? = "Back", + modifier: Modifier = Modifier, + ) { + val isPressed by interactionSource.collectIsPressedAsState() + val scaleX = animateFloatAsState( + targetValue = if (isPressed) 0.9f else 1f, + animationSpec = tween( + durationMillis = FluentDuration.ShortDuration, + easing = FluentEasing.FastInvokeEasing + ) + ) + FontIcon( + type = FontIconPrimitive.ChromeBack, + size = size, + contentDescription = contentDescription, + modifier = modifier.graphicsLayer { + this.scaleX = scaleX.value + translationX = (1f - scaleX.value) * 6.dp.toPx() + } + ) + } + + @Composable + fun NavigationIcon( + interactionSource: InteractionSource, + size: FontIconSize = FontIconSize.Standard, + contentDescription: String? = "Navigation", + modifier: Modifier = Modifier, + ) { + val isPressed by interactionSource.collectIsPressedAsState() + val scaleX = animateFloatAsState( + targetValue = if (isPressed) 0.6f else 1f, + animationSpec = tween( + durationMillis = FluentDuration.ShortDuration, + easing = FluentEasing.FastInvokeEasing + ) + ) + FontIcon( + type = FontIconPrimitive.GlobalNavigation, + size = size, + contentDescription = contentDescription, + modifier = modifier.graphicsLayer { + this.scaleX = scaleX.value + } + ) + } + + @Composable + fun SettingIcon( + interactionSource: InteractionSource, + size: FontIconSize = FontIconSize.Standard, + contentDescription: String? = "Settings", + modifier: Modifier = Modifier, + ) { + var latestPress by remember { mutableStateOf(null) } + LaunchedEffect(interactionSource) { + interactionSource.interactions.collect { value -> + when (value) { + is PressInteraction.Press -> latestPress = value + is PressInteraction.Release -> latestPress = value + is PressInteraction.Cancel -> latestPress = value + } + } + } + val transition = updateTransition(latestPress) + val rotation = transition.animateFloat( + transitionSpec = { + when { + initialState == null && targetState is PressInteraction.Press -> + tween(durationMillis = FluentDuration.ShortDuration, easing = FluentEasing.FastInvokeEasing) + + (initialState is PressInteraction.Press && targetState is PressInteraction.Release) || + (initialState is PressInteraction.Press && targetState is PressInteraction.Cancel) -> + tween(durationMillis = FluentDuration.LongDuration, easing = FluentEasing.FastInvokeEasing) + + else -> snap() + } + } + ){ + when(it) { + null -> 0f + is PressInteraction.Press -> -30f + else -> 360f + } + } + LaunchedEffect(transition.currentState, transition.isRunning) { + if (!transition.isRunning) { + if (transition.currentState is PressInteraction.Release || transition.currentState is PressInteraction.Cancel) { + latestPress = null + } + } + } + FontIcon( + type = FontIconPrimitive.Settings, + size = size, + contentDescription = contentDescription, + modifier = modifier.graphicsLayer { + rotationZ = rotation.value + } + ) + + } +} + +@Immutable +@JvmInline +value class FontIconSize(val value: Float) { + companion object { + val Standard = FontIconSize(16f) + val Small = FontIconSize(12f) + } } @Composable diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ListItem.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ListItem.kt index 2ab981a4..dbf8a700 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ListItem.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/ListItem.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.konyaco.fluent.FluentTheme import com.konyaco.fluent.LocalCompactMode import com.konyaco.fluent.LocalContentAlpha @@ -41,9 +40,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.Checkmark -import com.konyaco.fluent.icons.regular.ChevronRight import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualStateScheme import com.konyaco.fluent.scheme.collectVisualState @@ -225,12 +221,10 @@ object ListItemDefaults { @Composable fun CheckIcon() { - FontIcon( - glyph = '\uE8FB', - vector = Icons.Default.Checkmark, + FontIconSolid8( + type = FontIconPrimitive.Accept, contentDescription = "Check", - iconSize = 12.sp, - vectorSize = 12.dp + size = FontIconSize.Small, ) } @@ -247,11 +241,9 @@ object ListItemDefaults { @Composable fun CascadingIcon() { FontIcon( - glyph = '\uE974', - vector = Icons.Default.ChevronRight, + type = FontIconPrimitive.ChevronRight, contentDescription = "cascading", - vectorSize = 12.dp, - iconSize = 12.sp + size = FontIconSize.Small, ) } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/LiteFilter.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/LiteFilter.kt index dde97dcd..592dc931 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/LiteFilter.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/LiteFilter.kt @@ -35,9 +35,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.konyaco.fluent.animation.FluentDuration -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.CaretLeft -import com.konyaco.fluent.icons.filled.CaretRight import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @@ -75,7 +72,7 @@ fun LiteFilter( ) { SubtleButton( onClick = { scope.launch { state.animateScrollBy(-state.viewportSize / 3f) } }, - content = { Icon(Icons.Filled.CaretLeft, contentDescription = null) }, + content = { FontIconSolid8(type = FontIconPrimitive.CaretLeft, contentDescription = null) }, iconOnly = true ) } @@ -88,7 +85,7 @@ fun LiteFilter( ) { SubtleButton( onClick = { scope.launch { state.animateScrollBy(state.viewportSize / 3f) } }, - content = { Icon(Icons.Filled.CaretRight, contentDescription = null) }, + content = { FontIconSolid8(type = FontIconPrimitive.CaretRight, contentDescription = null) }, iconOnly = true ) } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/NavigationView.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/NavigationView.kt index d05520e2..817bcaf7 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/NavigationView.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/NavigationView.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box @@ -75,11 +77,9 @@ import com.konyaco.fluent.background.ElevationDefaults import com.konyaco.fluent.background.Layer import com.konyaco.fluent.background.MaterialContainer import com.konyaco.fluent.background.MaterialDefaults -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ArrowLeft -import com.konyaco.fluent.icons.regular.Navigation import com.konyaco.fluent.layout.overflow.OverflowRowScope import com.konyaco.fluent.scheme.PentaVisualScheme +import com.konyaco.fluent.scheme.collectVisualState import kotlin.math.roundToInt internal val LocalNavigationExpand = compositionLocalOf { false } @@ -1013,42 +1013,16 @@ object NavigationDefaults { @Composable fun ExpandedButton( onClick: () -> Unit, - icon: @Composable (() -> Unit) = { - FontIcon( - glyph = '\uE700', - vector = Icons.Default.Navigation, - contentDescription = "Expanded" - ) - }, modifier: Modifier = Modifier, disabled: Boolean = false, buttonColors: ButtonColorScheme = ButtonDefaults.subtleButtonColors(), interaction: MutableInteractionSource = remember { MutableInteractionSource() }, - animationEnabled: Boolean = true, + icon: @Composable (() -> Unit) = { FontIconDefaults.NavigationIcon(interaction) }, ) { Button( onClick = onClick, interaction = interaction, - icon = { - if (animationEnabled) { - val isPressed by interaction.collectIsPressedAsState() - val scaleX = animateFloatAsState( - targetValue = if (isPressed) 0.6f else 1f, - animationSpec = tween( - durationMillis = FluentDuration.ShortDuration, - easing = FluentEasing.FastInvokeEasing - ) - ) - Box( - content = { icon() }, - modifier = Modifier.graphicsLayer { - this.scaleX = scaleX.value - } - ) - } else { - icon() - } - }, + icon = { icon() }, modifier = modifier, disabled = disabled, buttonColors = buttonColors @@ -1080,44 +1054,17 @@ object NavigationDefaults { @Composable fun BackButton( onClick: () -> Unit, - icon: @Composable (() -> Unit) = { - FontIcon( - glyph = '\uE830', - vector = Icons.Default.ArrowLeft, - contentDescription = null, - ) - }, modifier: Modifier = Modifier, disabled: Boolean = false, buttonColors: ButtonColorScheme = ButtonDefaults.subtleButtonColors(), interaction: MutableInteractionSource = remember { MutableInteractionSource() }, - animationEnabled: Boolean = true, + icon: @Composable (() -> Unit) = { FontIconDefaults.BackIcon(interaction, size = FontIconSize.Small) }, ) { Button( onClick = onClick, iconOnly = true, interaction = interaction, - content = { - if (animationEnabled) { - val isPressed by interaction.collectIsPressedAsState() - val scaleX = animateFloatAsState( - targetValue = if (isPressed) 0.9f else 1f, - animationSpec = tween( - durationMillis = FluentDuration.ShortDuration, - easing = FluentEasing.FastInvokeEasing - ) - ) - Box( - content = { icon() }, - modifier = Modifier.graphicsLayer { - this.scaleX = scaleX.value - translationX = (1f - scaleX.value) * 6.dp.toPx() - } - ) - } else { - icon() - } - }, + content = { icon() }, modifier = modifier .size(44.dp, 40.dp) .padding(vertical = 2.dp) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/RatingControl.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/RatingControl.kt index 0ad573d8..12bda8b0 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/RatingControl.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/RatingControl.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable @@ -34,17 +33,14 @@ import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.konyaco.fluent.FluentTheme +import com.konyaco.fluent.LocalContentAlpha import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.ProvideTextStyle -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.Star -import com.konyaco.fluent.icons.regular.Star import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualStateScheme import com.konyaco.fluent.scheme.collectVisualState @@ -68,7 +64,7 @@ fun RatingControl( onValueChanged: (Float) -> Unit, modifier: Modifier = Modifier, colors: VisualStateScheme = RatingControlDefaults.colors(), - width: Dp = 20.dp, + width: Dp = 16.dp, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, placeholderValue: Float = 0f, maxRating: Int = 5, @@ -284,12 +280,12 @@ private fun drawStar( Row(horizontalArrangement = Arrangement.spacedBy(ratingSpacing), modifier = modifier) { val hasValue = value() != 0f val (icon, iconColor) = when { - selected -> Icons.Filled.Star to when { + selected -> FontIconPrimitive.FavoriteStarFull to when { (!hasValue && isHovered) || displayPlaceholder -> color.placeholderColor else -> color.selectedColor } - else -> Icons.Regular.Star to color.color + else -> FontIconPrimitive.RatingStar to color.color } repeat(maxRating) { index -> Box( @@ -297,21 +293,28 @@ private fun drawStar( onItemPositioned(index, it.boundsInParent()) } ) { - //TODO Update star icon - Icon( - imageVector = icon, - contentDescription = null, - tint = iconColor, - modifier = Modifier.size(width) - ) - if (isHovered && !hasValue && selected) { - Icon( - imageVector = Icons.Regular.Star, + CompositionLocalProvider( + LocalContentColor provides iconColor, + LocalContentAlpha provides iconColor.alpha + ) { + FontIcon( + type = icon, contentDescription = null, - tint = color.color, - modifier = Modifier.size(width) + size = FontIconSize(width.value) ) } + if (isHovered && !hasValue && selected) { + CompositionLocalProvider( + LocalContentColor provides color.color, + LocalContentAlpha provides color.color.alpha + ) { + FontIcon( + type = FontIconPrimitive.RatingStar, + contentDescription = null, + size = FontIconSize(width.value) + ) + } + } } diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Scrollbar.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Scrollbar.kt index 373d7ef0..4cbd6df0 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Scrollbar.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Scrollbar.kt @@ -12,7 +12,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -23,10 +29,10 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirstOrNull import com.konyaco.fluent.FluentTheme +import com.konyaco.fluent.LocalContentAlpha +import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.CaretRight import kotlinx.coroutines.launch /* @@ -173,39 +179,55 @@ fun ScrollbarIndicator( } ) val targetAlpha = animationFraction - Icon( - imageVector = Icons.Filled.CaretRight, - contentDescription = null, - tint = when { - pressed -> colors.contentColorPressed - hovered -> colors.contentColorHovered - !enabled -> colors.contentColorDisabled - else -> colors.contentColor - }, - modifier = Modifier.size(14.dp).then(modifier).clickable( - interactionSource = interaction, - indication = null, - enabled = enabled && visible - ) { - scrollScope.launch { - if (forward) { - adapter.scrollTo(-offset + adapter.scrollOffset) + val tint = when { + pressed -> colors.contentColorPressed + hovered -> colors.contentColorHovered + !enabled -> colors.contentColorDisabled + else -> colors.contentColor + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .then(modifier) + .then( + if (isVertical) { + Modifier.size(12.dp, 16.dp) } else { - adapter.scrollTo(offset + adapter.scrollOffset) + Modifier.size(16.dp, 12.dp) } - } - }.graphicsLayer { - scaleX = targetScale - scaleY = targetScale - rotationZ = when { - isVertical && forward -> 270f - isVertical -> 90f - forward -> 180f - else -> 0f - } - alpha = targetAlpha + ) + .clickable( + interactionSource = interaction, + indication = null, + enabled = enabled && visible + ) { + scrollScope.launch { + if (forward) { + adapter.scrollTo(-offset + adapter.scrollOffset) + } else { + adapter.scrollTo(offset + adapter.scrollOffset) + } + } + }.graphicsLayer { + scaleX = targetScale + scaleY = targetScale + alpha = targetAlpha + }) { + CompositionLocalProvider( + LocalContentColor provides tint, + LocalContentAlpha provides tint.alpha + ) { + FontIconSolid8( + type = when { + isVertical && forward -> FontIconPrimitive.CaretUp + isVertical -> FontIconPrimitive.CaretDown + forward -> FontIconPrimitive.CaretLeft + else -> FontIconPrimitive.CaretRight + }, + contentDescription = null + ) } - ) + } } @Composable @@ -239,12 +261,15 @@ fun ScrollbarContainer( } ) { measurables, constraints -> val contentMeasurable = - measurables.fastFirstOrNull { it.layoutId == "content" } ?: return@Layout layout(0, 0) {} - val contentPlaceable = contentMeasurable.measure(constraints.copy(minWidth = 0, minHeight = 0)) - val scrollbarMeasurable = measurables.fastFirstOrNull { it.layoutId == "scrollbar" } ?: return@Layout layout( - contentPlaceable.width, - contentPlaceable.height - ) { contentPlaceable.place(0, 0) } + measurables.fastFirstOrNull { it.layoutId == "content" } + ?: return@Layout layout(0, 0) {} + val contentPlaceable = + contentMeasurable.measure(constraints.copy(minWidth = 0, minHeight = 0)) + val scrollbarMeasurable = + measurables.fastFirstOrNull { it.layoutId == "scrollbar" } ?: return@Layout layout( + contentPlaceable.width, + contentPlaceable.height + ) { contentPlaceable.place(0, 0) } val scrollbarPlaceable = scrollbarMeasurable.measure( if (isVertical) { Constraints.fixedHeight(contentPlaceable.height) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/SideNav.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/SideNav.kt index 422125e6..4d5bac28 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/SideNav.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/SideNav.kt @@ -52,9 +52,6 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ChevronDown -import com.konyaco.fluent.icons.regular.Search import com.konyaco.fluent.scheme.collectVisualState import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -151,7 +148,7 @@ fun SideNav( }, icon = { - Icon(Icons.Default.Search, null) + FontIcon(type = FontIconPrimitive.Search, contentDescription = null) }, content = {} ) @@ -349,9 +346,8 @@ fun SideNavItem( ) ) FontIcon( - glyph = '\uE972', - iconSize = FontIconDefaults.fontSizeSmall, - vector = Icons.Default.ChevronDown, + type = FontIconPrimitive.ChevronDown, + size = FontIconSize.Small, contentDescription = null, modifier = Modifier .padding(start = 2.dp, end = 14.dp) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TabView.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TabView.kt index 39417b2f..a956b48c 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TabView.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TabView.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged @@ -71,18 +70,12 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import com.konyaco.fluent.ExperimentalFluentApi import com.konyaco.fluent.FluentTheme import com.konyaco.fluent.LocalContentAlpha import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.LocalTextStyle -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.filled.CaretLeft -import com.konyaco.fluent.icons.filled.CaretRight -import com.konyaco.fluent.icons.regular.Add -import com.konyaco.fluent.icons.regular.Dismiss import com.konyaco.fluent.scheme.PentaVisualScheme import com.konyaco.fluent.scheme.VisualStateScheme import com.konyaco.fluent.scheme.collectVisualState @@ -141,8 +134,7 @@ fun TabRow( AnimatedVisibility(displayScrollController) { TabScrollActionButton( onClick = { coroutineScope.launch { state.animateScrollBy(-state.layoutInfo.viewportSize.width / 3f) } }, - glyph = '\uEDD9', - vector = Icons.Filled.CaretLeft, + icon = FontIconPrimitive.CaretLeft, enabled = state.canScrollBackward, colors = scrollActionButtonColors, modifier = Modifier.padding(end = 4.dp) @@ -162,8 +154,7 @@ fun TabRow( AnimatedVisibility(displayScrollController) { TabScrollActionButton( onClick = { coroutineScope.launch { state.animateScrollBy(state.layoutInfo.viewportSize.width / 3f) } }, - glyph = '\uEDDA', - vector = Icons.Filled.CaretRight, + icon = FontIconPrimitive.CaretRight, enabled = state.canScrollForward, colors = scrollActionButtonColors, modifier = Modifier.padding(start = 4.dp) @@ -430,12 +421,7 @@ object TabViewDefaults { Button( onClick = onClick, content = { - FontIcon( - glyph = '\uE624', - vector = Icons.Default.Dismiss, - iconSize = 12.sp, - contentDescription = null - ) + FontIcon(type = FontIconPrimitive.Close, contentDescription = null) }, iconOnly = true, buttonColors = colors, @@ -459,11 +445,7 @@ object TabViewDefaults { Button( onClick = onClick, content = { - FontIcon( - glyph = '\uE710', - vector = Icons.Default.Add, - contentDescription = null - ) + FontIcon(type = FontIconPrimitive.Add, contentDescription = null) }, iconOnly = true, buttonColors = colors, @@ -552,8 +534,7 @@ class TabItemEndDividerController( @Composable private fun TabScrollActionButton( onClick: () -> Unit, - glyph: Char, - vector: ImageVector, + icon: FontIconPrimitive, modifier: Modifier = Modifier, enabled: Boolean = false, colors: VisualStateScheme @@ -561,12 +542,9 @@ private fun TabScrollActionButton( RepeatButton( onClick = onClick, content = { - FontIcon( - glyph = glyph, - vector = vector, + FontIconSolid8( + type = icon, contentDescription = null, - iconSize = 8.sp, - vectorSize = 14.dp, modifier = Modifier ) }, diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TextBoxButton.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TextBoxButton.kt index 45f8f9a9..09ee4fb9 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TextBoxButton.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TextBoxButton.kt @@ -19,18 +19,8 @@ import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.semantics.Role -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.konyaco.fluent.FluentTheme -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ArrowRight -import com.konyaco.fluent.icons.regular.ChevronDown -import com.konyaco.fluent.icons.regular.ChevronUp -import com.konyaco.fluent.icons.regular.Dismiss -import com.konyaco.fluent.icons.regular.Eye -import com.konyaco.fluent.icons.regular.Search import com.konyaco.fluent.scheme.VisualStateScheme import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -141,25 +131,25 @@ fun RepeatTextBoxButton( .focusProperties { canFocus = focusable } .pointerHoverIcon(PointerIcon.Default, !enabled) .combinedClickable( - interactionSource = interaction, - indication = null, - enabled = enabled, - onClick = onClick, - onLongClick = { - onClick() - scope.launch { - delay(delay) - do { - onClick() - delay(interval) - } while (pressed.value) + interactionSource = interaction, + indication = null, + enabled = enabled, + onClick = onClick, + onLongClick = { + onClick() + scope.launch { + delay(delay) + do { + onClick() + delay(interval) + } while (pressed.value) + } + }, + onDoubleClick = { + onClick() + onClick() } - }, - onDoubleClick = { - onClick() - onClick() - } - ), + ), interactionSource = interaction, enabled = enabled, colors = colors, @@ -171,89 +161,81 @@ fun RepeatTextBoxButton( object TextBoxButtonDefaults { - val iconFontSmallSize = 12.sp - - val iconVectorSmallSize = 12.dp - - val iconFontMediumSize = 16.sp - - val iconVectorMediumSize = 16.dp - @Composable - fun SearchIcon(fontSize: TextUnit = iconFontSmallSize, vectorSize: Dp = iconVectorSmallSize) { + fun SearchIcon( + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, + ) { FontIcon( - glyph = '\uF78B', - vector = Icons.Default.Search, + type = FontIconPrimitive.Search, contentDescription = "Search", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } @Composable - fun ClearIcon(fontSize: TextUnit = iconFontSmallSize, vectorSize: Dp = iconVectorSmallSize) { + fun ClearIcon( + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, + ) { FontIcon( - glyph = '\uE624', - vector = Icons.Default.Dismiss, + type = FontIconPrimitive.Close, contentDescription = "Clear", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } @Composable fun RevealPasswordIcon( - fontSize: TextUnit = iconFontSmallSize, - vectorSize: Dp = iconVectorSmallSize + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, ) { FontIcon( - glyph = '\uF78D', - vector = Icons.Default.Eye, + type = FontIconPrimitive.RevealPassword, contentDescription = "Reveal Password", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } @Composable fun ArrowRightIcon( - fontSize: TextUnit = iconFontSmallSize, - vectorSize: Dp = iconVectorSmallSize + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, ) { FontIcon( - glyph = '\uE64D', - vector = Icons.Default.ArrowRight, + type = FontIconPrimitive.ArrowRight, contentDescription = "Arrow Right", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } @Composable fun ChevronUpIcon( - fontSize: TextUnit = iconFontSmallSize, - vectorSize: Dp = iconVectorSmallSize + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, ) { FontIcon( - glyph = '\uE70E', - vector = Icons.Default.ChevronUp, + type = FontIconPrimitive.ChevronUp, contentDescription = "Chevron Up", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } @Composable fun ChevronDownIcon( - fontSize: TextUnit = iconFontSmallSize, - vectorSize: Dp = iconVectorSmallSize + size: FontIconSize = FontIconSize.Small, + modifier: Modifier = Modifier, ) { FontIcon( - glyph = '\uE70F', - vector = Icons.Default.ChevronDown, + type = FontIconPrimitive.ChevronDown, contentDescription = "Chevron Down", - iconSize = fontSize, - vectorSize = vectorSize + size = size, + modifier = modifier ) } } \ No newline at end of file diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TopNav.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TopNav.kt index d2ee0aed..39d92cbf 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TopNav.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/TopNav.kt @@ -35,9 +35,6 @@ import com.konyaco.fluent.LocalTextStyle import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.Layer -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ChevronDown -import com.konyaco.fluent.icons.regular.MoreHorizontal import com.konyaco.fluent.layout.HorizontalIndicatorContentLayout import com.konyaco.fluent.layout.overflow.OverflowRow import com.konyaco.fluent.layout.overflow.OverflowRowScope @@ -90,9 +87,8 @@ fun TopNav( selected = false, icon = { FontIcon( - glyph = '\uE712', - vector = Icons.Default.MoreHorizontal, - contentDescription = null, + type = FontIconPrimitive.More, + contentDescription = null ) }, onClick = { onExpandedChanged(true) } @@ -192,9 +188,8 @@ fun TopNavItem( } ) FontIcon( - glyph = '\uE972', - iconSize = FontIconDefaults.fontSizeSmall, - vector = Icons.Default.ChevronDown, + type = FontIconPrimitive.ChevronDown, + size = FontIconSize.Small, contentDescription = null, modifier = Modifier .graphicsLayer { diff --git a/fluent/src/desktopMain/kotlin/com/konyaco/fluent/component/ContextMenu.desktop.kt b/fluent/src/desktopMain/kotlin/com/konyaco/fluent/component/ContextMenu.desktop.kt index 7a1b2f6a..8e422e76 100644 --- a/fluent/src/desktopMain/kotlin/com/konyaco/fluent/component/ContextMenu.desktop.kt +++ b/fluent/src/desktopMain/kotlin/com/konyaco/fluent/component/ContextMenu.desktop.kt @@ -3,26 +3,48 @@ package com.konyaco.fluent.component import androidx.compose.animation.EnterTransition import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.ContextMenuArea +import androidx.compose.foundation.ContextMenuItem +import androidx.compose.foundation.ContextMenuRepresentation +import androidx.compose.foundation.ContextMenuState +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.TextContextMenu -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.key.* +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.isAltPressed +import androidx.compose.ui.input.key.isCtrlPressed +import androidx.compose.ui.input.key.isMetaPressed +import androidx.compose.ui.input.key.isShiftPressed +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLocalization -import androidx.compose.ui.unit.* +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.round +import androidx.compose.ui.unit.sp import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.Copy -import com.konyaco.fluent.icons.regular.Cut -import com.konyaco.fluent.icons.regular.ClipboardPaste +import com.konyaco.fluent.component.FluentContextMenuItem.KeyData import org.jetbrains.skiko.hostOs internal object FluentContextMenuRepresentation : ContextMenuRepresentation { + @Composable override fun Representation(state: ContextMenuState, items: () -> List) { var rect by remember { @@ -76,12 +98,17 @@ internal object FluentContextMenuRepresentation : ContextMenuRepresentation { }, icon = if (shouldPaddingIcon) { { + if (it.glyph != null && LocalFontIconFontFamily.current != null) { - FontIcon(it.glyph, modifier = Modifier) + FontIcon( + glyph = it.glyph, + vector = it.vector?.let { vector -> { vector } }, + contentDescription = it.label, + ) } else if (it.vector != null) { Icon( it.vector, it.label, - modifier = Modifier.size(with(LocalDensity.current) { ((FontIconDefaults.fontSizeStandard.value + 2).sp).toDp() }) + modifier = Modifier.size(with(LocalDensity.current) { ((FontIconSize.Standard.value + 2).sp).toDp() }) ) } } @@ -166,42 +193,53 @@ internal object FluentTextContextMenu : TextContextMenu { FluentContextMenuItem( label = localization.cut, onClick = it, - glyph = '\uE8C6', - vector = Icons.Default.Cut, - keyData = FluentContextMenuItem.KeyData(Key.X, isCtrlPressed = true) + icon = FontIconPrimitive.Cut, + keyData = KeyData(Key.X, isCtrlPressed = true) ) }, textManager.copy?.let { FluentContextMenuItem( label = localization.copy, onClick = it, - glyph = '\uE8C8', - vector = Icons.Default.Copy, - keyData = FluentContextMenuItem.KeyData(Key.C, isCtrlPressed = true) + icon = FontIconPrimitive.Copy, + keyData = KeyData(Key.C, isCtrlPressed = true) ) }, textManager.paste?.let { FluentContextMenuItem( label = localization.paste, onClick = it, - glyph = '\uE77F', - vector = Icons.Default.ClipboardPaste, - keyData = FluentContextMenuItem.KeyData(Key.V, isCtrlPressed = true) + icon = FontIconPrimitive.Paste, + keyData = KeyData(Key.V, isCtrlPressed = true) ) }, textManager.selectAll?.let { FluentContextMenuItem( label = localization.selectAll, onClick = it, - keyData = FluentContextMenuItem.KeyData(Key.A, isCtrlPressed = true), + keyData = KeyData(Key.A, isCtrlPressed = true), ) }, ) } ContextMenuArea(items, state, content = content) } + } +fun FluentContextMenuItem( + label: String, + onClick: () -> Unit, + icon: FontIconPrimitive, + keyData: KeyData? = null, +) = FluentContextMenuItem( + label = label, + onClick = onClick, + glyph = icon.glyph, + vector = icon.vector(), + keyData = keyData +) + class FluentContextMenuItem( label: String, onClick: () -> Unit, diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/component/CopyButton.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/component/CopyButton.kt index b76d693f..3f0a20f0 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/component/CopyButton.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/component/CopyButton.kt @@ -11,10 +11,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString import com.konyaco.fluent.component.Button -import com.konyaco.fluent.component.Icon -import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.Checkmark -import com.konyaco.fluent.icons.regular.Copy +import com.konyaco.fluent.component.FontIcon +import com.konyaco.fluent.component.FontIconPrimitive import kotlinx.coroutines.delay @Composable @@ -38,11 +36,14 @@ fun CopyButton( iconOnly = true, content = { AnimatedContent(isCopy) { target -> - if (target) { - Icon(Icons.Default.Checkmark, contentDescription = null) - } else { - Icon(Icons.Default.Copy, contentDescription = null) - } + FontIcon( + type = if (target) { + FontIconPrimitive.Accept + } else { + FontIconPrimitive.Copy + }, + contentDescription = null + ) } }, modifier = modifier diff --git a/gallery/src/desktopMain/kotlin/com/konyaco/fluent/gallery/window/WindowsWindowFrame.kt b/gallery/src/desktopMain/kotlin/com/konyaco/fluent/gallery/window/WindowsWindowFrame.kt index 9ddb84fe..50c4f78d 100644 --- a/gallery/src/desktopMain/kotlin/com/konyaco/fluent/gallery/window/WindowsWindowFrame.kt +++ b/gallery/src/desktopMain/kotlin/com/konyaco/fluent/gallery/window/WindowsWindowFrame.kt @@ -53,6 +53,8 @@ import com.konyaco.fluent.animation.FluentDuration import com.konyaco.fluent.animation.FluentEasing import com.konyaco.fluent.background.BackgroundSizing import com.konyaco.fluent.background.Layer +import com.konyaco.fluent.component.FontIconDefaults +import com.konyaco.fluent.component.FontIconSize import com.konyaco.fluent.component.NavigationDefaults import com.konyaco.fluent.component.Text import com.konyaco.fluent.gallery.jna.windows.ComposeWindowProcedure @@ -61,7 +63,6 @@ import com.konyaco.fluent.gallery.jna.windows.structure.WinUserConst.HTCLIENT import com.konyaco.fluent.gallery.jna.windows.structure.WinUserConst.HTMAXBUTTON import com.konyaco.fluent.gallery.jna.windows.structure.isWindows11OrLater import com.konyaco.fluent.icons.Icons -import com.konyaco.fluent.icons.regular.ArrowLeft import com.konyaco.fluent.icons.regular.Dismiss import com.konyaco.fluent.icons.regular.Square import com.konyaco.fluent.icons.regular.SquareMultiple @@ -148,16 +149,12 @@ fun FrameWindowScope.WindowsWindowFrame( } ) { if (it) { + val interactionSource = remember { MutableInteractionSource() } NavigationDefaults.BackButton( onClick = backButtonClick, disabled = !backButtonEnabled, - icon = { - Text( - text = CaptionButtonIcon.Back.glyph.toString(), - fontFamily = windowsFontFamily(), - fontSize = 10.sp - ) - } + interaction = interactionSource, + icon = { FontIconDefaults.BackIcon(interactionSource, size = FontIconSize(10f)) } ) } else { Spacer(modifier = Modifier.width(14.dp).height(36.dp)) @@ -401,10 +398,6 @@ enum class CaptionButtonIcon( Close( glyph = '\uE8BB', imageVector = Icons.Default.Dismiss - ), - Back( - glyph = '\uE830', - imageVector = Icons.Default.ArrowLeft ) }