Skip to content

Commit

Permalink
[fluent] feat: Add BackIcon and NavigationIcon for NavigationView
Browse files Browse the repository at this point in the history
This commit introduces two new icons, `BackIcon` and `NavigationIcon`, for use within the `NavigationView` component.

- `BackIcon`: Represents a back button and provides a subtle scale animation on press.
- `NavigationIcon`: Represents a navigation menu and provides a subtle scale animation on press.

These icons are now used by default for the `BackButton` and `ExpandedButton` composables within `NavigationView`. They enhance the visual feedback and consistency of the navigation experience.
  • Loading branch information
Sanlorng committed Nov 21, 2024
1 parent 8d1801a commit ea66a6a
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 64 deletions.
126 changes: 126 additions & 0 deletions fluent/src/commonMain/kotlin/com/konyaco/fluent/component/FontIcon.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
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.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
Expand All @@ -17,6 +31,8 @@ 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
Expand Down Expand Up @@ -181,6 +197,116 @@ fun FontIconSolid8(
)
}

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<PressInteraction?>(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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,6 +79,7 @@ import com.konyaco.fluent.background.MaterialContainer
import com.konyaco.fluent.background.MaterialDefaults
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 }
Expand Down Expand Up @@ -936,41 +939,16 @@ object NavigationDefaults {
@Composable
fun ExpandedButton(
onClick: () -> Unit,
icon: @Composable (() -> Unit) = {
FontIcon(
type = FontIconPrimitive.GlobalNavigation,
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
Expand Down Expand Up @@ -1002,40 +980,17 @@ object NavigationDefaults {
@Composable
fun BackButton(
onClick: () -> Unit,
icon: @Composable (() -> Unit) = {
FontIcon(type = FontIconPrimitive.ChromeBack, 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -401,10 +398,6 @@ enum class CaptionButtonIcon(
Close(
glyph = '\uE8BB',
imageVector = Icons.Default.Dismiss
),
Back(
glyph = '\uE830',
imageVector = Icons.Default.ArrowLeft
)
}

Expand Down

0 comments on commit ea66a6a

Please sign in to comment.