├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── lib ├── src │ ├── commonMain │ │ ├── resources │ │ │ └── font │ │ │ │ ├── segoe_small.ttf │ │ │ │ ├── segoe_text.ttf │ │ │ │ ├── segoe_display.ttf │ │ │ │ ├── segoe_small_bold.ttf │ │ │ │ ├── segoe_text_bold.ttf │ │ │ │ ├── segoe_text_light.ttf │ │ │ │ ├── segoe_display_bold.ttf │ │ │ │ ├── segoe_small_light.ttf │ │ │ │ ├── segoe_display_light.ttf │ │ │ │ ├── segoe_small_semibold.ttf │ │ │ │ ├── segoe_small_semilight.ttf │ │ │ │ ├── segoe_text_semibold.ttf │ │ │ │ ├── segoe_text_semilight.ttf │ │ │ │ ├── segoe_display_semibold.ttf │ │ │ │ └── segoe_display_semilight.ttf │ │ └── kotlin │ │ │ ├── Content.kt │ │ │ ├── Shape.kt │ │ │ ├── Animation.kt │ │ │ ├── Surface.kt │ │ │ ├── FluentTheme.kt │ │ │ ├── Interaction.kt │ │ │ ├── Text.kt │ │ │ ├── Focus.kt │ │ │ ├── Typography.kt │ │ │ ├── Hyperlink.kt │ │ │ ├── Elevation.kt │ │ │ ├── HyperlinkButton.kt │ │ │ ├── Button.kt │ │ │ ├── ToggleButton.kt │ │ │ ├── Radio.kt │ │ │ ├── ToggleSwitch.kt │ │ │ └── ColorScheme.kt │ └── jvmMain │ │ └── kotlin │ │ ├── Window.kt │ │ └── Elevation.jvm.kt └── build.gradle.kts ├── settings.gradle.kts ├── .gitignore ├── example ├── src │ └── jvmMain │ │ └── kotlin │ │ ├── Theme.kt │ │ └── Main.kt └── build.gradle.kts ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.version=1.6.10 3 | compose.version=1.1.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_small.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_text.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_text.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_display.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_display.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_small_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_small_bold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_text_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_text_bold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_text_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_text_light.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_display_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_display_bold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_small_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_small_light.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_display_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_display_light.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_small_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_small_semibold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_small_semilight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_small_semilight.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_text_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_text_semibold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_text_semilight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_text_semilight.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_display_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_display_semibold.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/resources/font/segoe_display_semilight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotrols/Fluent/HEAD/lib/src/commonMain/resources/font/segoe_display_semilight.ttf -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Content.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.staticCompositionLocalOf 2 | import androidx.compose.ui.graphics.Color 3 | 4 | val LocalContentColor = staticCompositionLocalOf { Color.Black } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 7 | } 8 | 9 | plugins { 10 | kotlin("multiplatform").version(extra["kotlin.version"] as String) 11 | id("org.jetbrains.compose").version(extra["compose.version"] as String) 12 | } 13 | } 14 | 15 | rootProject.name = "KotrolsFluent" 16 | 17 | include("example") 18 | include("lib") -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Shape.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.shape.RoundedCornerShape 2 | import androidx.compose.runtime.Immutable 3 | import androidx.compose.runtime.staticCompositionLocalOf 4 | import androidx.compose.ui.unit.dp 5 | 6 | @Immutable 7 | data class Shapes( 8 | val extraSmall: RoundedCornerShape = RoundedCornerShape(3.dp), 9 | val small: RoundedCornerShape = RoundedCornerShape(4.dp), 10 | val medium: RoundedCornerShape = RoundedCornerShape(7.dp), 11 | val large: RoundedCornerShape = RoundedCornerShape(8.dp) 12 | ) 13 | 14 | internal val LocalShapes = staticCompositionLocalOf { Shapes() } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | *.iws 10 | *.iml 11 | *.ipr 12 | out/ 13 | !**/src/main/**/out/ 14 | !**/src/test/**/out/ 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | bin/ 25 | !**/src/main/**/bin/ 26 | !**/src/test/**/bin/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store -------------------------------------------------------------------------------- /lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.compose 2 | 3 | plugins { 4 | kotlin("multiplatform") 5 | id("org.jetbrains.compose") 6 | } 7 | 8 | group = "com.kotrols" 9 | version = "1.0-SNAPSHOT" 10 | 11 | kotlin { 12 | jvm { 13 | compilations.all { 14 | kotlinOptions.jvmTarget = "11" 15 | } 16 | withJava() 17 | } 18 | sourceSets { 19 | named("commonMain") { 20 | dependencies { 21 | implementation(compose.foundation) 22 | } 23 | } 24 | named("jvmMain") { 25 | dependencies { 26 | implementation("com.mayakapps.compose:window-styler:0.3.2") 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/src/jvmMain/kotlin/Theme.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | 3 | @Composable 4 | fun FluentExampleTheme( 5 | isDark: Boolean = false, 6 | content: @Composable () -> Unit 7 | ) { 8 | val colorScheme = when (isDark) { 9 | true -> darkColorScheme() 10 | false -> lightColorScheme() 11 | } 12 | val borderElevation = when (isDark) { 13 | true -> darkBorderElevation() 14 | false -> lightBorderElevation() 15 | } 16 | val shadowElevation = when (isDark) { 17 | true -> darkShadowElevation() 18 | false -> lightShadowElevation() 19 | } 20 | FluentTheme( 21 | colorScheme = colorScheme, 22 | borderElevation = borderElevation, 23 | shadowElevation = shadowElevation, 24 | content = content 25 | ) 26 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Animation.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.core.* 2 | import androidx.compose.runtime.Composable 3 | import androidx.compose.runtime.State 4 | import androidx.compose.ui.BiasAlignment 5 | 6 | @Composable 7 | fun animateBiasAlignmentAsState( 8 | targetValue: BiasAlignment, 9 | animationSpec: AnimationSpec = spring(), 10 | finishedListener: ((BiasAlignment) -> Unit)? = null 11 | ): State { 12 | return animateValueAsState( 13 | targetValue, 14 | typeConverter = TwoWayConverter( 15 | convertToVector = { 16 | AnimationVector(it.horizontalBias, it.verticalBias) 17 | }, 18 | convertFromVector = { 19 | BiasAlignment(it.v1, it.v2) 20 | }, 21 | ), 22 | animationSpec = animationSpec, 23 | finishedListener = finishedListener, 24 | ) 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotrols/Fluent 2 | Fluent Design controls for Jetpack Compose. 3 | 4 | # Credits 5 | Huge thanks to [MayakaApps](https://github.com/MayakaApps) and their [ComposeWindowStyler](https://github.com/MayakaApps/ComposeWindowStyler) which powers the window backdrop management for this library. 6 | 7 | License 8 | ------- 9 | ``` 10 | Copyright (C) 2022 X1nto. 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | ``` -------------------------------------------------------------------------------- /example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.compose 2 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 3 | 4 | plugins { 5 | kotlin("multiplatform") 6 | id("org.jetbrains.compose") 7 | } 8 | 9 | kotlin { 10 | jvm { 11 | compilations.all { 12 | kotlinOptions.jvmTarget = "11" 13 | } 14 | withJava() 15 | } 16 | sourceSets { 17 | named("jvmMain") { 18 | dependencies { 19 | implementation(project(":lib")) 20 | implementation(compose.desktop.currentOs) 21 | } 22 | } 23 | } 24 | } 25 | 26 | compose.desktop { 27 | application { 28 | mainClass = "MainKt" 29 | nativeDistributions { 30 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 31 | packageName = "com.kotrols.fluent.demo" 32 | packageVersion = "1.0.0" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Surface.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.Canvas 2 | import androidx.compose.foundation.layout.Box 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.draw.blur 6 | import androidx.compose.ui.graphics.BlendMode 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.unit.dp 9 | 10 | @Composable 11 | fun AcrylicSurface( 12 | modifier: Modifier = Modifier, 13 | color: Color = FluentTheme.colorScheme.backgroundAcrylicDefault, 14 | content: @Composable () -> Unit 15 | ) { 16 | Box( 17 | modifier = modifier, 18 | propagateMinConstraints = true 19 | ) { 20 | Canvas(modifier.matchParentSize().blur(60.dp)) { 21 | drawRect( 22 | color = color, 23 | blendMode = BlendMode.Luminosity 24 | ) 25 | } 26 | content() 27 | } 28 | } 29 | 30 | @Composable 31 | fun MicaSurface( 32 | modifier: Modifier = Modifier, 33 | color: Color = FluentTheme.colorScheme.backgroundMicaBase, 34 | content: @Composable () -> Unit 35 | ) { 36 | Box( 37 | modifier = modifier, 38 | propagateMinConstraints = true 39 | ) { 40 | Canvas(modifier.matchParentSize().blur(240.dp)) { 41 | drawRect( 42 | color = color, 43 | blendMode = BlendMode.Luminosity 44 | ) 45 | } 46 | content() 47 | } 48 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/FluentTheme.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import androidx.compose.runtime.CompositionLocalProvider 3 | import androidx.compose.runtime.ReadOnlyComposable 4 | 5 | @Composable 6 | fun FluentTheme( 7 | colorScheme: ColorScheme = lightColorScheme(), 8 | typography: Typography = Typography(), 9 | borderElevation: BorderElevation = lightBorderElevation(), 10 | shadowElevation: ShadowElevation = lightShadowElevation(), 11 | shapes: Shapes = Shapes(), 12 | content: @Composable () -> Unit 13 | ) { 14 | CompositionLocalProvider( 15 | LocalColorScheme provides colorScheme, 16 | LocalTypography provides typography, 17 | LocalBorderElevation provides borderElevation, 18 | LocalShadowElevation provides shadowElevation, 19 | LocalShapes provides shapes, 20 | ) { 21 | content() 22 | } 23 | } 24 | 25 | object FluentTheme { 26 | 27 | val colorScheme 28 | @Composable 29 | @ReadOnlyComposable 30 | get() = LocalColorScheme.current 31 | 32 | val typography 33 | @Composable 34 | @ReadOnlyComposable 35 | get() = LocalTypography.current 36 | 37 | val borderElevation 38 | @Composable 39 | @ReadOnlyComposable 40 | get() = LocalBorderElevation.current 41 | 42 | val shadowElevation 43 | @Composable 44 | @ReadOnlyComposable 45 | get() = LocalShadowElevation.current 46 | 47 | val shapes 48 | @Composable 49 | @ReadOnlyComposable 50 | get() = LocalShapes.current 51 | 52 | } -------------------------------------------------------------------------------- /example/src/jvmMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.layout.* 2 | import androidx.compose.runtime.* 3 | import androidx.compose.ui.Alignment 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.unit.dp 6 | import androidx.compose.ui.window.application 7 | 8 | @Composable 9 | fun App( 10 | isDark: Boolean, 11 | requestThemeChange: (dark: Boolean) -> Unit, 12 | ) { 13 | FluentExampleTheme(isDark = isDark) { 14 | Column( 15 | modifier = Modifier.fillMaxSize(), 16 | verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), 17 | horizontalAlignment = Alignment.CenterHorizontally 18 | ) { 19 | RadioGroupColumn( 20 | header = { 21 | Text("Theme") 22 | } 23 | ) { 24 | RadioButton( 25 | selected = !isDark, 26 | onSelect = { 27 | requestThemeChange(false) 28 | } 29 | ) { 30 | Text("Light") 31 | } 32 | RadioButton( 33 | selected = isDark, 34 | onSelect = { 35 | requestThemeChange(true) 36 | } 37 | ) { 38 | Text("Dark") 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | fun main() = application { 46 | var isDark by remember { mutableStateOf(false) } 47 | MicaWindow( 48 | onCloseRequest = ::exitApplication, 49 | isDark = isDark, 50 | title = "Compose Window", 51 | ) { 52 | App( 53 | isDark = isDark, 54 | requestThemeChange = { dark -> 55 | isDark = dark 56 | } 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Interaction.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.interaction.* 2 | import androidx.compose.runtime.* 3 | import kotlinx.coroutines.flow.collect 4 | 5 | @Composable 6 | fun InteractionSource.collectInteractionAsState(): State { 7 | val interactions = remember { mutableStateListOf() } 8 | LaunchedEffect(this) { 9 | this@collectInteractionAsState.interactions.collect { interaction -> 10 | when (interaction) { 11 | is HoverInteraction.Enter -> { 12 | interactions.add(interaction) 13 | } 14 | 15 | is HoverInteraction.Exit -> { 16 | interactions.remove(interaction.enter) 17 | } 18 | 19 | is FocusInteraction.Focus -> { 20 | interactions.add(interaction) 21 | } 22 | 23 | is FocusInteraction.Unfocus -> { 24 | interactions.remove(interaction.focus) 25 | } 26 | 27 | is PressInteraction.Press -> { 28 | interactions.add(interaction) 29 | } 30 | 31 | is PressInteraction.Release -> { 32 | interactions.remove(interaction.press) 33 | } 34 | 35 | is PressInteraction.Cancel -> { 36 | interactions.remove(interaction.press) 37 | } 38 | 39 | is DragInteraction.Start -> { 40 | interactions.add(interaction) 41 | } 42 | 43 | is DragInteraction.Stop -> { 44 | interactions.remove(interaction.start) 45 | } 46 | 47 | is DragInteraction.Cancel -> { 48 | interactions.remove(interaction.start) 49 | } 50 | } 51 | } 52 | } 53 | return rememberUpdatedState(interactions.lastOrNull()) 54 | } -------------------------------------------------------------------------------- /lib/src/jvmMain/kotlin/Window.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import androidx.compose.runtime.SideEffect 3 | import androidx.compose.runtime.remember 4 | import androidx.compose.ui.graphics.painter.Painter 5 | import androidx.compose.ui.input.key.KeyEvent 6 | import androidx.compose.ui.window.FrameWindowScope 7 | import androidx.compose.ui.window.Window 8 | import androidx.compose.ui.window.WindowState 9 | import androidx.compose.ui.window.rememberWindowState 10 | import com.mayakapps.compose.windowstyler.WindowBackdrop 11 | import com.mayakapps.compose.windowstyler.WindowStyleManager 12 | 13 | @Composable 14 | fun MicaWindow( 15 | isDark: Boolean, 16 | onCloseRequest: () -> Unit, 17 | state: WindowState = rememberWindowState(), 18 | visible: Boolean = true, 19 | title: String = "Untitled", 20 | icon: Painter? = null, 21 | undecorated: Boolean = false, 22 | transparent: Boolean = false, 23 | resizable: Boolean = true, 24 | enabled: Boolean = true, 25 | focusable: Boolean = true, 26 | alwaysOnTop: Boolean = false, 27 | onPreviewKeyEvent: (KeyEvent) -> Boolean = { false }, 28 | onKeyEvent: (KeyEvent) -> Boolean = { false }, 29 | content: @Composable FrameWindowScope.() -> Unit 30 | ) { 31 | Window( 32 | onCloseRequest = onCloseRequest, 33 | state = state, 34 | visible = visible, 35 | title = title, 36 | icon = icon, 37 | undecorated = undecorated, 38 | transparent = transparent, 39 | resizable = resizable, 40 | enabled = enabled, 41 | focusable = focusable, 42 | alwaysOnTop = alwaysOnTop, 43 | onPreviewKeyEvent = onPreviewKeyEvent, 44 | onKeyEvent = onKeyEvent, 45 | ) { 46 | val windowStyleManager = remember(window) { 47 | WindowStyleManager(window, isDarkTheme = isDark, backdropType = WindowBackdrop.Mica) 48 | } 49 | SideEffect { 50 | windowStyleManager.isDarkTheme = isDark 51 | } 52 | content() 53 | } 54 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Text.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.text.BasicText 2 | import androidx.compose.foundation.text.InlineTextContent 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.AnnotatedString 8 | import androidx.compose.ui.text.TextLayoutResult 9 | import androidx.compose.ui.text.TextStyle 10 | import androidx.compose.ui.text.style.TextOverflow 11 | 12 | @Composable 13 | fun Text( 14 | text: String, 15 | modifier: Modifier = Modifier, 16 | style: TextStyle = LocalTextStyle.current, 17 | color: Color = LocalContentColor.current, 18 | onTextLayout: (TextLayoutResult) -> Unit = {}, 19 | overflow: TextOverflow = TextOverflow.Clip, 20 | softWrap: Boolean = true, 21 | maxLines: Int = Int.MAX_VALUE 22 | ) { 23 | val mergedStyle = style.merge( 24 | TextStyle( 25 | color = color 26 | ) 27 | ) 28 | BasicText( 29 | text = text, 30 | modifier = modifier, 31 | style = mergedStyle, 32 | onTextLayout = onTextLayout, 33 | overflow = overflow, 34 | softWrap = softWrap, 35 | maxLines = maxLines 36 | ) 37 | } 38 | 39 | @Composable 40 | fun Text( 41 | text: AnnotatedString, 42 | modifier: Modifier = Modifier, 43 | style: TextStyle = LocalTextStyle.current, 44 | color: Color = LocalContentColor.current, 45 | onTextLayout: (TextLayoutResult) -> Unit = {}, 46 | overflow: TextOverflow = TextOverflow.Clip, 47 | softWrap: Boolean = true, 48 | maxLines: Int = Int.MAX_VALUE, 49 | inlineContent: Map = mapOf(), 50 | ) { 51 | val mergedStyle = style.merge( 52 | TextStyle( 53 | color = color 54 | ) 55 | ) 56 | BasicText( 57 | text = text, 58 | modifier = modifier, 59 | style = mergedStyle, 60 | onTextLayout = onTextLayout, 61 | overflow = overflow, 62 | softWrap = softWrap, 63 | maxLines = maxLines, 64 | inlineContent = inlineContent, 65 | ) 66 | } 67 | 68 | internal val LocalTextStyle = staticCompositionLocalOf { TextStyle.Default } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Focus.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.Modifier 2 | import androidx.compose.ui.draw.drawBehind 3 | import androidx.compose.ui.geometry.Size 4 | import androidx.compose.ui.graphics.Color 5 | import androidx.compose.ui.graphics.Shape 6 | import androidx.compose.ui.graphics.drawOutline 7 | import androidx.compose.ui.graphics.drawscope.DrawScope 8 | import androidx.compose.ui.graphics.drawscope.Stroke 9 | import androidx.compose.ui.graphics.drawscope.translate 10 | import androidx.compose.ui.unit.dp 11 | 12 | internal fun Modifier.focusStroke( 13 | outerColor: Color, 14 | innerColor: Color, 15 | shape: Shape 16 | ) = drawBehind { 17 | val innerStrokeWidth = 2.dp.toPx() 18 | val outerStrokeWidth = 3.dp.toPx() 19 | 20 | val innerStrokeOffset = 1.dp.toPx() 21 | val innerStrokeTranslateOffset = -(innerStrokeOffset / 2) 22 | val outerStrokeOffset = innerStrokeWidth + (outerStrokeWidth / 2) 23 | val outerStrokeTranslateOffset = -(outerStrokeOffset / 2) 24 | 25 | translateEvenly(innerStrokeTranslateOffset) { 26 | drawOutline( 27 | outline = shape.createOutline( 28 | size = size + Size(innerStrokeOffset), 29 | layoutDirection = layoutDirection, 30 | density = this 31 | ), 32 | color = innerColor, 33 | style = Stroke(innerStrokeWidth), 34 | ) 35 | } 36 | translateEvenly(outerStrokeTranslateOffset + innerStrokeTranslateOffset) { 37 | drawOutline( 38 | outline = shape.createOutline( 39 | size = size + Size(outerStrokeOffset + innerStrokeOffset), 40 | layoutDirection = layoutDirection, 41 | density = this 42 | ), 43 | color = outerColor, 44 | style = Stroke(outerStrokeWidth), 45 | ) 46 | } 47 | } 48 | 49 | private inline fun DrawScope.translateEvenly( 50 | topLeft: Float, 51 | block: DrawScope.() -> Unit, 52 | ) { 53 | translate( 54 | top = topLeft, 55 | left = topLeft, 56 | block = block 57 | ) 58 | } 59 | 60 | private fun Size(size: Float): Size = Size(width = size, height = size) 61 | 62 | private operator fun Size.plus(other: Size): Size { 63 | return Size(this.width + other.width, this.height + other.height) 64 | } -------------------------------------------------------------------------------- /lib/src/jvmMain/kotlin/Elevation.jvm.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.Modifier 2 | import androidx.compose.ui.graphics.* 3 | import org.jetbrains.skia.ImageFilter 4 | 5 | actual fun Modifier.shadows(shadows: List, shape: Shape): Modifier { 6 | return if (shadows.isNotEmpty()) { 7 | val filter = if (shadows.size == 1) { 8 | val shadow = shadows.first() 9 | shadow.toImageFilterShadow() 10 | } else { 11 | ImageFilter.makeMerge( 12 | filters = shadows.map { 13 | it.toImageFilterShadow() 14 | }.toTypedArray(), 15 | crop = null 16 | ) 17 | } 18 | graphicsLayer( 19 | renderEffect = filter.asComposeRenderEffect() 20 | ) 21 | // drawBehind { 22 | // val irect = when (val outline = shape.createOutline(size, layoutDirection, this)) { 23 | // is Outline.Generic -> { 24 | // IRect.makeLTRB( 25 | // l = outline.bounds.left.roundToInt(), 26 | // r = outline.bounds.right.roundToInt(), 27 | // t = outline.bounds.top.roundToInt(), 28 | // b = outline.bounds.bottom.roundToInt() 29 | // ) 30 | // } 31 | // is Outline.Rectangle -> { 32 | // IRect.makeLTRB( 33 | // l = outline.rect.left.roundToInt(), 34 | // r = outline.rect.right.roundToInt(), 35 | // t = outline.rect.top.roundToInt(), 36 | // b = outline.rect.bottom.roundToInt() 37 | // ) 38 | // } 39 | // is Outline.Rounded -> { 40 | // IRect.makeLTRB( 41 | // l = outline.roundRect.left.roundToInt(), 42 | // r = outline.roundRect.right.roundToInt(), 43 | // t = outline.roundRect.top.roundToInt(), 44 | // b = outline.roundRect.bottom.roundToInt() 45 | // ) 46 | // } 47 | // } 48 | // drawIntoCanvas { 49 | // val frameworkPaint = Paint().asFrameworkPaint() 50 | // frameworkPaint.imageFilter = filter 51 | // } 52 | // } 53 | } else Modifier 54 | } 55 | 56 | private fun Shadow.toImageFilterShadow(): ImageFilter { 57 | return ImageFilter.makeDropShadow( 58 | dx = this.offset.x, 59 | dy = this.offset.y, 60 | sigmaX = blurRadius, 61 | sigmaY = blurRadius, 62 | color = this.color.toArgb(), 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Typography.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Immutable 2 | import androidx.compose.runtime.staticCompositionLocalOf 3 | import androidx.compose.ui.text.TextStyle 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | import androidx.compose.ui.text.platform.Font 7 | import androidx.compose.ui.unit.sp 8 | 9 | val SegoeUIText = retrieveFontFamily("text") 10 | val SegoeUIDisplay = retrieveFontFamily("display") 11 | val SegoeUISmall = retrieveFontFamily("small") 12 | 13 | @Immutable 14 | data class Typography( 15 | val caption: TextStyle = TextStyle( 16 | fontFamily = SegoeUISmall, 17 | fontWeight = FontWeight.W400, 18 | fontSize = 12.sp, 19 | lineHeight = 16.sp, 20 | ), 21 | val body: TextStyle = TextStyle( 22 | fontFamily = SegoeUIText, 23 | fontWeight = FontWeight.W400, 24 | fontSize = 14.sp, 25 | lineHeight = 20.sp, 26 | ), 27 | val bodyStrong: TextStyle = TextStyle( 28 | fontFamily = SegoeUIText, 29 | fontWeight = FontWeight.W600, 30 | fontSize = 14.sp, 31 | lineHeight = 20.sp 32 | ), 33 | val bodyLarge: TextStyle = TextStyle( 34 | fontFamily = SegoeUIText, 35 | fontWeight = FontWeight.W400, 36 | fontSize = 18.sp, 37 | lineHeight = 24.sp 38 | ), 39 | val subtitle: TextStyle = TextStyle( 40 | fontFamily = SegoeUIDisplay, 41 | fontWeight = FontWeight.W600, 42 | fontSize = 20.sp, 43 | lineHeight = 28.sp 44 | ), 45 | val title: TextStyle = TextStyle( 46 | fontFamily = SegoeUIDisplay, 47 | fontWeight = FontWeight.W600, 48 | fontSize = 28.sp, 49 | lineHeight = 36.sp 50 | ), 51 | val titleLarge: TextStyle = TextStyle( 52 | fontFamily = SegoeUIDisplay, 53 | fontWeight = FontWeight.W600, 54 | fontSize = 40.sp, 55 | lineHeight = 52.sp 56 | ), 57 | val display: TextStyle = TextStyle( 58 | fontFamily = SegoeUIDisplay, 59 | fontWeight = FontWeight.W600, 60 | fontSize = 68.sp, 61 | lineHeight = 92.sp 62 | ), 63 | ) 64 | 65 | internal val LocalTypography = staticCompositionLocalOf { Typography() } 66 | 67 | private fun retrieveFontFamily(fontType: String): FontFamily { 68 | return FontFamily( 69 | Font( 70 | resource = "font/segoe_${fontType}_light.ttf", 71 | weight = FontWeight.Light 72 | ), 73 | Font( 74 | resource = "font/segoe_${fontType}_semilight.ttf", 75 | weight = FontWeight(350) 76 | ), 77 | Font( 78 | resource = "font/segoe_${fontType}.ttf", 79 | weight = FontWeight.Normal 80 | ), 81 | Font( 82 | resource = "font/segoe_${fontType}_semibold.ttf", 83 | weight = FontWeight.SemiBold 84 | ), 85 | Font( 86 | resource = "font/segoe_${fontType}_bold.ttf", 87 | weight = FontWeight.Bold 88 | ), 89 | ) 90 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Hyperlink.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.clickable 2 | import androidx.compose.foundation.focusable 3 | import androidx.compose.foundation.interaction.* 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.ExperimentalComposeUiApi 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.input.pointer.PointerIconDefaults 9 | import androidx.compose.ui.input.pointer.pointerHoverIcon 10 | import androidx.compose.ui.semantics.Role 11 | import androidx.compose.ui.text.TextStyle 12 | import androidx.compose.ui.text.style.TextDecoration 13 | 14 | @OptIn(ExperimentalComposeUiApi::class) 15 | @Composable 16 | fun Hyperlink( 17 | onClick: () -> Unit, 18 | text: String, 19 | style: TextStyle = FluentTheme.typography.body, 20 | modifier: Modifier = Modifier, 21 | enabled: Boolean = true, 22 | colors: HyperlinkColors = HyperlinkDefaults.hyperlinkColors(), 23 | focus: HyperlinkFocus = HyperlinkDefaults.hyperlinkFocus(), 24 | decoration: HyperlinkDecoration = HyperlinkDefaults.hyperlinkDecoration(), 25 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 26 | ) { 27 | val textColor by colors.color(enabled, interactionSource) 28 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 29 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 30 | val textDecoration by decoration.decoration(enabled, interactionSource) 31 | val textStyle = style.copy(textDecoration = textDecoration) 32 | Text( 33 | modifier = modifier 34 | .focusStroke( 35 | outerColor = focusOuterStroke, 36 | innerColor = focusInnerStroke, 37 | shape = FluentTheme.shapes.small 38 | ) 39 | .clickable( 40 | onClick = onClick, 41 | indication = null, 42 | interactionSource = interactionSource, 43 | enabled = enabled, 44 | role = Role.Button 45 | ) 46 | .focusable( 47 | enabled = enabled, 48 | interactionSource = interactionSource 49 | ) 50 | .pointerHoverIcon(PointerIconDefaults.Hand), 51 | text = text, 52 | style = textStyle, 53 | color = textColor 54 | ) 55 | } 56 | 57 | object HyperlinkDefaults { 58 | 59 | @Composable 60 | fun hyperlinkColors( 61 | textColor: Color = FluentTheme.colorScheme.textAccentPrimary, 62 | textHoverColor: Color = FluentTheme.colorScheme.textAccentSecondary, 63 | textPressedColor: Color = FluentTheme.colorScheme.textAccentTertiary, 64 | textDisabledColor: Color = FluentTheme.colorScheme.textAccentDisabled, 65 | textFocusedColor: Color = textColor, 66 | ): HyperlinkColors { 67 | return DefaultHyperlinkColors( 68 | textColor = textColor, 69 | textHoverColor = textHoverColor, 70 | textPressedColor = textPressedColor, 71 | textDisabledColor = textDisabledColor, 72 | textFocusedColor = textFocusedColor 73 | ) 74 | } 75 | 76 | @Composable 77 | fun hyperlinkDecoration( 78 | decoration: TextDecoration? = TextDecoration.Underline, 79 | decorationHover: TextDecoration? = null, 80 | decorationPressed: TextDecoration? = null, 81 | decorationDisabled: TextDecoration? = decoration, 82 | decorationFocused: TextDecoration? = decoration 83 | ): HyperlinkDecoration { 84 | return DefaultHyperlinkDecoration( 85 | decoration = decoration, 86 | decorationHover = decorationHover, 87 | decorationPressed = decorationPressed, 88 | decorationDisabled = decorationDisabled, 89 | decorationFocused = decorationFocused 90 | ) 91 | } 92 | 93 | @Composable 94 | fun hyperlinkFocus( 95 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 96 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 97 | ): HyperlinkFocus { 98 | return DefaultHyperlinkFocus( 99 | innerStroke = innerStroke, 100 | outerStroke = outerStroke 101 | ) 102 | } 103 | 104 | } 105 | 106 | interface HyperlinkColors { 107 | 108 | @Composable 109 | fun color(enabled: Boolean, interactionSource: InteractionSource): State 110 | 111 | } 112 | 113 | interface HyperlinkDecoration { 114 | 115 | @Composable 116 | fun decoration(enabled: Boolean, interactionSource: InteractionSource): State 117 | 118 | } 119 | 120 | interface HyperlinkFocus { 121 | 122 | @Composable 123 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 124 | 125 | @Composable 126 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 127 | 128 | } 129 | 130 | @Immutable 131 | data class DefaultHyperlinkColors( 132 | private val textColor: Color, 133 | private val textHoverColor: Color, 134 | private val textPressedColor: Color, 135 | private val textDisabledColor: Color, 136 | private val textFocusedColor: Color 137 | ) : HyperlinkColors { 138 | 139 | @Composable 140 | override fun color(enabled: Boolean, interactionSource: InteractionSource): State { 141 | val interaction by interactionSource.collectInteractionAsState() 142 | 143 | val target = if (!enabled) { 144 | textDisabledColor 145 | } else { 146 | when (interaction) { 147 | is PressInteraction.Press -> textPressedColor 148 | is HoverInteraction.Enter -> textHoverColor 149 | is FocusInteraction.Focus -> textFocusedColor 150 | else -> textColor 151 | } 152 | } 153 | return rememberUpdatedState(target) 154 | } 155 | } 156 | 157 | @Immutable 158 | data class DefaultHyperlinkDecoration( 159 | private val decoration: TextDecoration?, 160 | private val decorationHover: TextDecoration?, 161 | private val decorationPressed: TextDecoration?, 162 | private val decorationDisabled: TextDecoration?, 163 | private val decorationFocused: TextDecoration? 164 | ) : HyperlinkDecoration { 165 | 166 | @Composable 167 | override fun decoration(enabled: Boolean, interactionSource: InteractionSource): State { 168 | val interaction by interactionSource.collectInteractionAsState() 169 | 170 | val target = if (!enabled) { 171 | decorationDisabled 172 | } else { 173 | when (interaction) { 174 | is PressInteraction.Press -> decorationPressed 175 | is HoverInteraction.Enter -> decorationHover 176 | is FocusInteraction.Focus -> decorationFocused 177 | else -> decoration 178 | } 179 | } 180 | return rememberUpdatedState(target) 181 | } 182 | } 183 | 184 | @Immutable 185 | data class DefaultHyperlinkFocus( 186 | private val innerStroke: Color, 187 | private val outerStroke: Color, 188 | ) : HyperlinkFocus { 189 | 190 | @Composable 191 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 192 | val isFocused by interactionSource.collectIsFocusedAsState() 193 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 194 | } 195 | 196 | @Composable 197 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 198 | val isFocused by interactionSource.collectIsFocusedAsState() 199 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 200 | } 201 | 202 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Elevation.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Immutable 2 | import androidx.compose.runtime.staticCompositionLocalOf 3 | import androidx.compose.ui.Modifier 4 | import androidx.compose.ui.geometry.Offset 5 | import androidx.compose.ui.graphics.* 6 | 7 | fun lightBorderElevation( 8 | control: Brush = Brush.linearGradient( 9 | 0.82f to Color(0x0F000000), 10 | 0.91f to Color(0x0F000000), 11 | 1f to Color(0x29000000), 12 | start = Offset.Zero, 13 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 14 | ), 15 | circle: Brush = Brush.linearGradient( 16 | 0.5f to Color(0x0F000000), 17 | 0.95f to Color(0x29000000), 18 | start = Offset.Zero, 19 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 20 | ), 21 | textControl: Brush = Brush.linearGradient( 22 | 1f to Color(0x0F000000), 23 | 1f to Color(0x72000000), 24 | start = Offset.Zero, 25 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 26 | ), 27 | textControlFocused: Brush = Brush.linearGradient( 28 | 0.97f to Color(0x0F000000), 29 | 0.97f to Color(0xFF0067C0), 30 | start = Offset.Zero, 31 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 32 | ), 33 | accentControl: Brush = Brush.linearGradient( 34 | 0.91f to Color(0x14FFFFFF), 35 | 1f to Color(0x66000000), 36 | start = Offset.Zero, 37 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 38 | ) 39 | ): BorderElevation { 40 | return BorderElevation( 41 | control = control, 42 | circle = circle, 43 | textControl = textControl, 44 | textControlFocused = textControlFocused, 45 | accentControl = accentControl 46 | ) 47 | } 48 | 49 | fun darkBorderElevation( 50 | control: Brush = Brush.linearGradient( 51 | 0f to Color(0x18FFFFFF), 52 | 0.1f to Color(0x12FFFFFF), 53 | start = Offset.Zero, 54 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 55 | ), 56 | circle: Brush = Brush.linearGradient( 57 | 0f to Color(0x18FFFFFF), 58 | 0.5f to Color(0x12FFFFFF), 59 | start = Offset.Zero, 60 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 61 | ), 62 | textControl: Brush = Brush.linearGradient( 63 | 1f to Color(0x14FFFFFF), 64 | 1f to Color(0x8BFFFFFF), 65 | 1f to Color(0x8BFFFFFF), 66 | start = Offset.Zero, 67 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 68 | ), 69 | textControlFocused: Brush = Brush.linearGradient( 70 | 0.97f to Color(0x14FFFFFF), 71 | 0.97f to Color(0xFF4CC2FF), 72 | start = Offset.Zero, 73 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 74 | ), 75 | accentControl: Brush = Brush.linearGradient( 76 | 0.91f to Color(0x14FFFFFF), 77 | 1f to Color(0x24000000), 78 | start = Offset.Zero, 79 | end = Offset(x = 0f, y = Float.POSITIVE_INFINITY) 80 | ) 81 | ): BorderElevation { 82 | return BorderElevation( 83 | control = control, 84 | circle = circle, 85 | textControl = textControl, 86 | textControlFocused = textControlFocused, 87 | accentControl = accentControl 88 | ) 89 | } 90 | 91 | @Immutable 92 | data class BorderElevation( 93 | val control: Brush, 94 | val circle: Brush, 95 | val textControl: Brush, 96 | val textControlFocused: Brush, 97 | val accentControl: Brush 98 | ) 99 | 100 | fun lightShadowElevation( 101 | cardRest: List = listOf( 102 | Shadow( 103 | color = Color(0x0A000000), 104 | offset = Offset(x = 0f, y = 2f), 105 | blurRadius = 4f 106 | ) 107 | ), 108 | cardHover: List = listOf( 109 | Shadow( 110 | color = Color(0x1A000000), 111 | offset = Offset(x = 0f, y = 2f), 112 | blurRadius = 4f 113 | ) 114 | ), 115 | tooltip: List = listOf( 116 | Shadow( 117 | color = Color(0x24000000), 118 | offset = Offset(x = 0f, y = 4f), 119 | blurRadius = 8f 120 | ) 121 | ), 122 | flyout: List = listOf( 123 | Shadow( 124 | color = Color(0x24000000), 125 | offset = Offset(x = 0f, y = 8f), 126 | blurRadius = 16f 127 | ) 128 | ), 129 | dialog: List = listOf( 130 | Shadow( 131 | color = Color(0x26000000), 132 | offset = Offset(x = 0f, y = 2f), 133 | blurRadius = 21f 134 | ), 135 | Shadow( 136 | color = Color(0x30000000), 137 | offset = Offset(x = 0f, y = 32f), 138 | blurRadius = 64f 139 | ), 140 | ), 141 | inactiveWindow: List = listOf( 142 | Shadow( 143 | color = Color(0x26000000), 144 | offset = Offset(x = 10.67f, y = 2f), 145 | blurRadius = 21f 146 | ), 147 | Shadow( 148 | color = Color(0x30000000), 149 | offset = Offset(x = 0f, y = 16f), 150 | blurRadius = 32f 151 | ), 152 | ), 153 | activeWindow: List = listOf( 154 | Shadow( 155 | color = Color(0x38000000), 156 | offset = Offset(x = 0f, y = 2f), 157 | blurRadius = 21f 158 | ), 159 | Shadow( 160 | color = Color(0x47000000), 161 | offset = Offset(x = 0f, y = 32f), 162 | blurRadius = 64f 163 | ), 164 | ) 165 | ): ShadowElevation { 166 | return ShadowElevation( 167 | cardRest = cardRest, 168 | cardHover = cardHover, 169 | tooltip = tooltip, 170 | flyout = flyout, 171 | dialog = dialog, 172 | inactiveWindow = inactiveWindow, 173 | activeWindow = activeWindow 174 | ) 175 | } 176 | 177 | fun darkShadowElevation( 178 | cardRest: List = listOf( 179 | Shadow( 180 | color = Color(0x21000000), 181 | offset = Offset(x = 0f, y = 2f), 182 | blurRadius = 4f 183 | ) 184 | ), 185 | cardHover: List = listOf( 186 | Shadow( 187 | color = Color(0x42000000), 188 | offset = Offset(x = 0f, y = 2f), 189 | blurRadius = 4f 190 | ) 191 | ), 192 | tooltip: List = listOf( 193 | Shadow( 194 | color = Color(0x42000000), 195 | offset = Offset(x = 0f, y = 4f), 196 | blurRadius = 8f 197 | ) 198 | ), 199 | flyout: List = listOf( 200 | Shadow( 201 | color = Color(0x42000000), 202 | offset = Offset(x = 0f, y = 8f), 203 | blurRadius = 16f 204 | ) 205 | ), 206 | dialog: List = listOf( 207 | Shadow( 208 | color = Color(0x5E000000), 209 | offset = Offset(x = 0f, y = 2f), 210 | blurRadius = 21f 211 | ), 212 | Shadow( 213 | color = Color(0x5E000000), 214 | offset = Offset(x = 0f, y = 32f), 215 | blurRadius = 64f 216 | ), 217 | ), 218 | inactiveWindow: List = listOf( 219 | Shadow( 220 | color = Color(0x5E000000), 221 | offset = Offset(x = 10.67f, y = 2f), 222 | blurRadius = 21f 223 | ), 224 | Shadow( 225 | color = Color(0x5E000000), 226 | offset = Offset(x = 0f, y = 16f), 227 | blurRadius = 32f 228 | ), 229 | ), 230 | activeWindow: List = listOf( 231 | Shadow( 232 | color = Color(0x8C000000), 233 | offset = Offset(x = 0f, y = 2f), 234 | blurRadius = 21f 235 | ), 236 | Shadow( 237 | color = Color(0x8F000000), 238 | offset = Offset(x = 0f, y = 32f), 239 | blurRadius = 64f 240 | ), 241 | ) 242 | ): ShadowElevation { 243 | return ShadowElevation( 244 | cardRest = cardRest, 245 | cardHover = cardHover, 246 | tooltip = tooltip, 247 | flyout = flyout, 248 | dialog = dialog, 249 | inactiveWindow = inactiveWindow, 250 | activeWindow = activeWindow 251 | ) 252 | } 253 | 254 | @Immutable 255 | data class ShadowElevation( 256 | val cardRest: List, 257 | val cardHover: List, 258 | val tooltip: List, 259 | val flyout: List, 260 | val dialog: List, 261 | val inactiveWindow: List, 262 | val activeWindow: List 263 | ) 264 | 265 | expect fun Modifier.shadows(shadows: List, shape: Shape): Modifier 266 | 267 | internal val LocalBorderElevation = staticCompositionLocalOf { lightBorderElevation() } 268 | internal val LocalShadowElevation = staticCompositionLocalOf { lightShadowElevation() } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | Please set the JAVA_HOME variable in your environment to match the 137 | location of your Java installation." 138 | fi 139 | 140 | # Increase the maximum file descriptors if we can. 141 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 142 | case $MAX_FD in #( 143 | max*) 144 | MAX_FD=$( ulimit -H -n ) || 145 | warn "Could not query maximum file descriptor limit" 146 | esac 147 | case $MAX_FD in #( 148 | '' | soft) :;; #( 149 | *) 150 | ulimit -n "$MAX_FD" || 151 | warn "Could not set maximum file descriptor limit to $MAX_FD" 152 | esac 153 | fi 154 | 155 | # Collect all arguments for the java command, stacking in reverse order: 156 | # * args from the command line 157 | # * the main class name 158 | # * -classpath 159 | # * -D...appname settings 160 | # * --module-path (only if needed) 161 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 162 | 163 | # For Cygwin or MSYS, switch paths to Windows format before running java 164 | if "$cygwin" || "$msys" ; then 165 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 166 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 167 | 168 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 169 | 170 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 171 | for arg do 172 | if 173 | case $arg in #( 174 | -*) false ;; # don't mess with options #( 175 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 176 | [ -e "$t" ] ;; #( 177 | *) false ;; 178 | esac 179 | then 180 | arg=$( cygpath --path --ignore --mixed "$arg" ) 181 | fi 182 | # Roll the args list around exactly as many times as the number of 183 | # args, so each arg winds up back in the position where it started, but 184 | # possibly modified. 185 | # 186 | # NB: a `for` loop captures its iteration list before it begins, so 187 | # changing the positional parameters here affects neither the number of 188 | # iterations, nor the values presented in `arg`. 189 | shift # remove old arg 190 | set -- "$@" "$arg" # push replacement arg 191 | done 192 | fi 193 | 194 | # Collect all arguments for the java command; 195 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 196 | # shell script including quotes and variable substitutions, so put them in 197 | # double quotes to make sure that they get re-expanded; and 198 | # * put everything else in single quotes, so that it's not re-expanded. 199 | 200 | set -- \ 201 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 202 | -classpath "$CLASSPATH" \ 203 | org.gradle.wrapper.GradleWrapperMain \ 204 | "$@" 205 | 206 | # Use "xargs" to parse quoted args. 207 | # 208 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 209 | # 210 | # In Bash we could simply go: 211 | # 212 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 213 | # set -- "${ARGS[@]}" "$@" 214 | # 215 | # but POSIX shell has neither arrays nor command substitution, so instead we 216 | # post-process each arg (as a line of input to sed) to backslash-escape any 217 | # character that might be a shell metacharacter, then use eval to reverse 218 | # that process (while maintaining the separation between arguments), and wrap 219 | # the whole thing up as a single "set" statement. 220 | # 221 | # This will of course break if any of these variables contains a newline or 222 | # an unmatched quote. 223 | # 224 | 225 | eval "set -- $( 226 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 227 | xargs -n1 | 228 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 229 | tr '\n' ' ' 230 | )" '"$@"' 231 | 232 | exec "$JAVACMD" "$@" -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/HyperlinkButton.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.background 2 | import androidx.compose.foundation.clickable 3 | import androidx.compose.foundation.focusable 4 | import androidx.compose.foundation.interaction.* 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.ExperimentalComposeUiApi 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.clip 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.graphics.Shape 13 | import androidx.compose.ui.input.pointer.PointerIconDefaults 14 | import androidx.compose.ui.input.pointer.pointerHoverIcon 15 | import androidx.compose.ui.semantics.Role 16 | import androidx.compose.ui.unit.dp 17 | 18 | @OptIn(ExperimentalComposeUiApi::class) 19 | @Composable 20 | fun HyperlinkButton( 21 | onClick: () -> Unit, 22 | modifier: Modifier = Modifier, 23 | enabled: Boolean = true, 24 | colors: HyperlinkButtonColors = HyperlinkButtonDefaults.hyperlinkButtonColors(), 25 | focus: HyperlinkButtonFocus = HyperlinkButtonDefaults.hyperlinkButtonFocus(), 26 | shape: Shape = HyperlinkButtonDefaults.Shape, 27 | contentPadding: PaddingValues = HyperlinkButtonDefaults.ContentPadding, 28 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 29 | content: @Composable RowScope.() -> Unit 30 | ) { 31 | val backgroundColor by colors.backgroundColor(enabled, interactionSource) 32 | val contentColor by colors.contentColor(enabled, interactionSource) 33 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 34 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 35 | Box( 36 | modifier = modifier 37 | .focusStroke( 38 | outerColor = focusOuterStroke, 39 | innerColor = focusInnerStroke, 40 | shape = shape 41 | ) 42 | .clip(shape) 43 | .background(backgroundColor) 44 | .clickable( 45 | onClick = onClick, 46 | indication = null, 47 | interactionSource = interactionSource, 48 | enabled = enabled, 49 | role = Role.Button 50 | ) 51 | .focusable( 52 | enabled = enabled, 53 | interactionSource = interactionSource 54 | ) 55 | .pointerHoverIcon(PointerIconDefaults.Hand) 56 | ) { 57 | CompositionLocalProvider( 58 | LocalContentColor provides contentColor, 59 | LocalTextStyle provides FluentTheme.typography.body 60 | ) { 61 | Row( 62 | modifier = Modifier 63 | .defaultMinSize( 64 | minWidth = HyperlinkButtonDefaults.MinWidth, 65 | minHeight = HyperlinkButtonDefaults.MinHeight 66 | ) 67 | .padding(contentPadding), 68 | verticalAlignment = Alignment.CenterVertically, 69 | horizontalArrangement = Arrangement.Center, 70 | content = content 71 | ) 72 | } 73 | } 74 | } 75 | 76 | object HyperlinkButtonDefaults { 77 | 78 | val MinWidth = 50.dp 79 | val MinHeight = 32.dp 80 | 81 | val ContentPadding = PaddingValues( 82 | start = 12.dp, 83 | end = 12.dp, 84 | top = 5.dp, 85 | bottom = 7.dp 86 | ) 87 | 88 | val Shape 89 | @Composable 90 | @ReadOnlyComposable 91 | get() = FluentTheme.shapes.small 92 | 93 | @Composable 94 | fun hyperlinkButtonColors( 95 | backgroundColor: Color = FluentTheme.colorScheme.fillSubtleTransparent, 96 | backgroundHoverColor: Color = FluentTheme.colorScheme.fillSubtleSecondary, 97 | backgroundPressedColor: Color = FluentTheme.colorScheme.fillSubtleTertiary, 98 | backgroundDisabledColor: Color = FluentTheme.colorScheme.fillSubtleDisabled, 99 | backgroundFocusedColor: Color = backgroundColor, 100 | contentColor: Color = FluentTheme.colorScheme.textAccentPrimary, 101 | contentHoverColor: Color = FluentTheme.colorScheme.textAccentSecondary, 102 | contentPressedColor: Color = FluentTheme.colorScheme.textAccentTertiary, 103 | contentDisabledColor: Color = FluentTheme.colorScheme.textAccentDisabled, 104 | contentFocusedColor: Color = contentColor, 105 | ): HyperlinkButtonColors { 106 | return DefaultHyperlinkButtonColors( 107 | backgroundColor = backgroundColor, 108 | backgroundHoverColor = backgroundHoverColor, 109 | backgroundPressedColor = backgroundPressedColor, 110 | backgroundDisabledColor = backgroundDisabledColor, 111 | backgroundFocusedColor = backgroundFocusedColor, 112 | contentColor = contentColor, 113 | contentHoverColor = contentHoverColor, 114 | contentPressedColor = contentPressedColor, 115 | contentDisabledColor = contentDisabledColor, 116 | contentFocusedColor = contentFocusedColor, 117 | ) 118 | } 119 | 120 | @Composable 121 | fun hyperlinkButtonFocus( 122 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 123 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 124 | ): HyperlinkButtonFocus { 125 | return DefaultHyperlinkButtonFocus( 126 | innerStroke = innerStroke, 127 | outerStroke = outerStroke 128 | ) 129 | } 130 | 131 | } 132 | 133 | interface HyperlinkButtonColors { 134 | 135 | @Composable 136 | fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State 137 | 138 | @Composable 139 | fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State 140 | 141 | } 142 | 143 | interface HyperlinkButtonFocus { 144 | 145 | @Composable 146 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 147 | 148 | @Composable 149 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 150 | 151 | } 152 | 153 | @Immutable 154 | data class DefaultHyperlinkButtonColors( 155 | private val backgroundColor: Color, 156 | private val backgroundHoverColor: Color, 157 | private val backgroundPressedColor: Color, 158 | private val backgroundDisabledColor: Color, 159 | private val backgroundFocusedColor: Color, 160 | private val contentColor: Color, 161 | private val contentHoverColor: Color, 162 | private val contentPressedColor: Color, 163 | private val contentDisabledColor: Color, 164 | private val contentFocusedColor: Color, 165 | ) : HyperlinkButtonColors { 166 | 167 | @Composable 168 | override fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State { 169 | val interaction by interactionSource.collectInteractionAsState() 170 | 171 | val target = if (!enabled) { 172 | backgroundDisabledColor 173 | } else { 174 | when (interaction) { 175 | is PressInteraction.Press -> backgroundPressedColor 176 | is HoverInteraction.Enter -> backgroundHoverColor 177 | is FocusInteraction.Focus -> backgroundFocusedColor 178 | else -> backgroundColor 179 | } 180 | } 181 | return rememberUpdatedState(target) 182 | } 183 | 184 | @Composable 185 | override fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State { 186 | val interaction by interactionSource.collectInteractionAsState() 187 | 188 | val target = if (!enabled) { 189 | contentDisabledColor 190 | } else { 191 | when (interaction) { 192 | is PressInteraction.Press -> contentPressedColor 193 | is HoverInteraction.Enter -> contentHoverColor 194 | is FocusInteraction.Focus -> contentFocusedColor 195 | else -> contentColor 196 | } 197 | } 198 | return rememberUpdatedState(target) 199 | } 200 | } 201 | 202 | @Immutable 203 | data class DefaultHyperlinkButtonFocus( 204 | private val innerStroke: Color, 205 | private val outerStroke: Color, 206 | ) : HyperlinkButtonFocus { 207 | 208 | @Composable 209 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 210 | val isFocused by interactionSource.collectIsFocusedAsState() 211 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 212 | } 213 | 214 | @Composable 215 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 216 | val isFocused by interactionSource.collectIsFocusedAsState() 217 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 218 | } 219 | 220 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Button.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.* 2 | import androidx.compose.foundation.interaction.* 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.Alignment 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.draw.clip 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.graphics.Shape 10 | import androidx.compose.ui.semantics.Role 11 | import androidx.compose.ui.unit.dp 12 | 13 | @Composable 14 | fun Button( 15 | onClick: () -> Unit, 16 | modifier: Modifier = Modifier, 17 | enabled: Boolean = true, 18 | colors: ButtonColors = ButtonDefaults.standardButtonColors(), 19 | border: ButtonBorders = ButtonDefaults.standardButtonBorders(), 20 | focus: ButtonFocus = ButtonDefaults.buttonFocus(), 21 | shape: Shape = ButtonDefaults.Shape, 22 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 23 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 24 | content: @Composable RowScope.() -> Unit 25 | ) { 26 | val backgroundColor by colors.backgroundColor(enabled, interactionSource) 27 | val contentColor by colors.contentColor(enabled, interactionSource) 28 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 29 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 30 | val borderStroke by border.border(enabled, interactionSource) 31 | Box( 32 | modifier = modifier 33 | .focusStroke( 34 | outerColor = focusOuterStroke, 35 | innerColor = focusInnerStroke, 36 | shape = shape 37 | ) 38 | .clip(shape) 39 | .background(backgroundColor) 40 | .clickable( 41 | onClick = onClick, 42 | indication = null, 43 | interactionSource = interactionSource, 44 | enabled = enabled, 45 | role = Role.Button 46 | ) 47 | .focusable( 48 | enabled = enabled, 49 | interactionSource = interactionSource 50 | ) 51 | .then(if (borderStroke != null) Modifier.border(borderStroke!!, shape) else Modifier) 52 | ) { 53 | CompositionLocalProvider( 54 | LocalContentColor provides contentColor, 55 | LocalTextStyle provides FluentTheme.typography.body 56 | ) { 57 | Row( 58 | modifier = Modifier 59 | .defaultMinSize( 60 | minWidth = ButtonDefaults.MinWidth, 61 | minHeight = ButtonDefaults.MinHeight 62 | ) 63 | .padding(contentPadding), 64 | verticalAlignment = Alignment.CenterVertically, 65 | horizontalArrangement = Arrangement.Center, 66 | content = content 67 | ) 68 | } 69 | } 70 | } 71 | 72 | 73 | @Composable 74 | fun AccentButton( 75 | onClick: () -> Unit, 76 | modifier: Modifier = Modifier, 77 | enabled: Boolean = true, 78 | colors: ButtonColors = ButtonDefaults.accentButtonColors(), 79 | border: ButtonBorders = ButtonDefaults.accentButtonBorders(), 80 | focus: ButtonFocus = ButtonDefaults.buttonFocus(), 81 | shape: Shape = ButtonDefaults.Shape, 82 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 83 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 84 | content: @Composable RowScope.() -> Unit 85 | ) = Button( 86 | onClick = onClick, 87 | modifier = modifier, 88 | enabled = enabled, 89 | colors = colors, 90 | border = border, 91 | focus = focus, 92 | shape = shape, 93 | contentPadding = contentPadding, 94 | interactionSource = interactionSource, 95 | content = content 96 | ) 97 | 98 | object ButtonDefaults { 99 | 100 | val MinWidth = 120.dp 101 | val MinHeight = 32.dp 102 | 103 | val ContentPadding = PaddingValues( 104 | start = 12.dp, 105 | end = 12.dp, 106 | top = 5.dp, 107 | bottom = 7.dp 108 | ) 109 | 110 | val Shape 111 | @Composable 112 | @ReadOnlyComposable 113 | get() = FluentTheme.shapes.small 114 | 115 | @Composable 116 | fun standardButtonColors( 117 | backgroundColor: Color = FluentTheme.colorScheme.fillControlDefault, 118 | backgroundHoverColor: Color = FluentTheme.colorScheme.fillControlSecondary, 119 | backgroundPressedColor: Color = FluentTheme.colorScheme.fillControlTertiary, 120 | backgroundDisabledColor: Color = FluentTheme.colorScheme.fillControlDisabled, 121 | backgroundFocusedColor: Color = backgroundColor, 122 | contentColor: Color = FluentTheme.colorScheme.textPrimary, 123 | contentHoverColor: Color = FluentTheme.colorScheme.textPrimary, 124 | contentPressedColor: Color = FluentTheme.colorScheme.textSecondary, 125 | contentDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 126 | contentFocusedColor: Color = contentColor, 127 | ): ButtonColors { 128 | return DefaultButtonColors( 129 | backgroundColor = backgroundColor, 130 | backgroundHoverColor = backgroundHoverColor, 131 | backgroundPressedColor = backgroundPressedColor, 132 | backgroundDisabledColor = backgroundDisabledColor, 133 | backgroundFocusedColor = backgroundFocusedColor, 134 | contentColor = contentColor, 135 | contentHoverColor = contentHoverColor, 136 | contentPressedColor = contentPressedColor, 137 | contentDisabledColor = contentDisabledColor, 138 | contentFocusedColor = contentFocusedColor, 139 | ) 140 | } 141 | 142 | @Composable 143 | fun accentButtonColors( 144 | backgroundColor: Color = FluentTheme.colorScheme.fillAccentDefault, 145 | backgroundHoverColor: Color = FluentTheme.colorScheme.fillAccentSecondary, 146 | backgroundPressedColor: Color = FluentTheme.colorScheme.fillAccentTertiary, 147 | backgroundDisabledColor: Color = FluentTheme.colorScheme.fillAccentDisabled, 148 | backgroundFocusedColor: Color = backgroundColor, 149 | contentColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 150 | contentHoverColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 151 | contentPressedColor: Color = FluentTheme.colorScheme.textOnAccentSecondary, 152 | contentDisabledColor: Color = FluentTheme.colorScheme.textOnAccentDisabled, 153 | contentFocusedColor: Color = contentColor, 154 | ): ButtonColors { 155 | return DefaultButtonColors( 156 | backgroundColor = backgroundColor, 157 | backgroundHoverColor = backgroundHoverColor, 158 | backgroundPressedColor = backgroundPressedColor, 159 | backgroundDisabledColor = backgroundDisabledColor, 160 | backgroundFocusedColor = backgroundFocusedColor, 161 | contentColor = contentColor, 162 | contentHoverColor = contentHoverColor, 163 | contentPressedColor = contentPressedColor, 164 | contentDisabledColor = contentDisabledColor, 165 | contentFocusedColor = contentFocusedColor, 166 | ) 167 | } 168 | 169 | @Composable 170 | fun standardButtonBorders( 171 | stroke: BorderStroke? = BorderStroke( 172 | width = 1.dp, 173 | brush = FluentTheme.borderElevation.control 174 | ), 175 | strokeHover: BorderStroke? = stroke, 176 | strokePressed: BorderStroke? = BorderStroke( 177 | width = 1.dp, 178 | color = FluentTheme.colorScheme.strokeControlDefault 179 | ), 180 | strokeDisabled: BorderStroke? = BorderStroke( 181 | width = 1.dp, 182 | color = FluentTheme.colorScheme.strokeControlDefault 183 | ), 184 | strokeFocused: BorderStroke? = stroke, 185 | ): ButtonBorders { 186 | return DefaultButtonBorders( 187 | stroke = stroke, 188 | strokeHover = strokeHover, 189 | strokePressed = strokePressed, 190 | strokeDisabled = strokeDisabled, 191 | strokeFocused = strokeFocused 192 | ) 193 | } 194 | 195 | @Composable 196 | fun accentButtonBorders( 197 | stroke: BorderStroke? = BorderStroke( 198 | width = 1.dp, 199 | brush = FluentTheme.borderElevation.accentControl 200 | ), 201 | strokeHover: BorderStroke? = stroke, 202 | strokePressed: BorderStroke? = BorderStroke( 203 | width = 1.dp, 204 | color = FluentTheme.colorScheme.strokeControlOnAccentDefault 205 | ), 206 | strokeDisabled: BorderStroke? = null, 207 | strokeFocused: BorderStroke? = stroke, 208 | ): ButtonBorders { 209 | return DefaultButtonBorders( 210 | stroke = stroke, 211 | strokeHover = strokeHover, 212 | strokePressed = strokePressed, 213 | strokeDisabled = strokeDisabled, 214 | strokeFocused = strokeFocused 215 | ) 216 | } 217 | 218 | @Composable 219 | fun buttonFocus( 220 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 221 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 222 | ): ButtonFocus { 223 | return DefaultButtonFocus( 224 | innerStroke = innerStroke, 225 | outerStroke = outerStroke 226 | ) 227 | } 228 | 229 | } 230 | 231 | interface ButtonColors { 232 | 233 | @Composable 234 | fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State 235 | 236 | @Composable 237 | fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State 238 | 239 | } 240 | 241 | interface ButtonBorders { 242 | 243 | @Composable 244 | fun border(enabled: Boolean, interactionSource: InteractionSource): State 245 | 246 | } 247 | 248 | interface ButtonFocus { 249 | 250 | @Composable 251 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 252 | 253 | @Composable 254 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 255 | 256 | } 257 | 258 | @Immutable 259 | data class DefaultButtonColors( 260 | private val backgroundColor: Color, 261 | private val backgroundHoverColor: Color, 262 | private val backgroundPressedColor: Color, 263 | private val backgroundDisabledColor: Color, 264 | private val backgroundFocusedColor: Color, 265 | private val contentColor: Color, 266 | private val contentHoverColor: Color, 267 | private val contentPressedColor: Color, 268 | private val contentDisabledColor: Color, 269 | private val contentFocusedColor: Color, 270 | ) : ButtonColors { 271 | 272 | @Composable 273 | override fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State { 274 | val interaction by interactionSource.collectInteractionAsState() 275 | val target = if (!enabled) { 276 | backgroundDisabledColor 277 | } else { 278 | when (interaction) { 279 | is PressInteraction.Press -> backgroundPressedColor 280 | is HoverInteraction.Enter -> backgroundHoverColor 281 | is FocusInteraction.Focus -> backgroundFocusedColor 282 | else -> backgroundColor 283 | } 284 | } 285 | return rememberUpdatedState(target) 286 | } 287 | 288 | @Composable 289 | override fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State { 290 | val interaction by interactionSource.collectInteractionAsState() 291 | 292 | val target = if (!enabled) { 293 | contentDisabledColor 294 | } else { 295 | when (interaction) { 296 | is PressInteraction.Press -> contentPressedColor 297 | is HoverInteraction.Enter -> contentHoverColor 298 | is FocusInteraction.Focus -> contentFocusedColor 299 | else -> contentColor 300 | } 301 | } 302 | return rememberUpdatedState(target) 303 | } 304 | } 305 | 306 | @Immutable 307 | data class DefaultButtonBorders( 308 | private val stroke: BorderStroke?, 309 | private val strokeHover: BorderStroke?, 310 | private val strokePressed: BorderStroke?, 311 | private val strokeDisabled: BorderStroke?, 312 | private val strokeFocused: BorderStroke?, 313 | ) : ButtonBorders { 314 | 315 | @Composable 316 | override fun border(enabled: Boolean, interactionSource: InteractionSource): State { 317 | val interaction by interactionSource.collectInteractionAsState() 318 | 319 | val target = if (!enabled) { 320 | strokeDisabled 321 | } else { 322 | when (interaction) { 323 | is PressInteraction.Press -> strokePressed 324 | is HoverInteraction.Enter -> strokeHover 325 | is FocusInteraction.Focus -> strokeFocused 326 | else -> stroke 327 | } 328 | } 329 | return rememberUpdatedState(target) 330 | } 331 | 332 | } 333 | 334 | @Immutable 335 | data class DefaultButtonFocus( 336 | private val innerStroke: Color, 337 | private val outerStroke: Color, 338 | ) : ButtonFocus { 339 | 340 | @Composable 341 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 342 | val isFocused by interactionSource.collectIsFocusedAsState() 343 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 344 | } 345 | 346 | @Composable 347 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 348 | val isFocused by interactionSource.collectIsFocusedAsState() 349 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 350 | } 351 | 352 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/ToggleButton.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.foundation.BorderStroke 2 | import androidx.compose.foundation.background 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.focusable 5 | import androidx.compose.foundation.interaction.* 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.foundation.selection.toggleable 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.graphics.Shape 14 | import androidx.compose.ui.semantics.Role 15 | import androidx.compose.ui.unit.dp 16 | 17 | @Composable 18 | fun ToggleButton( 19 | onToggle: (Boolean) -> Unit, 20 | toggled: Boolean, 21 | modifier: Modifier = Modifier, 22 | enabled: Boolean = true, 23 | colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(), 24 | border: ToggleButtonBorders = ToggleButtonDefaults.toggleButtonBorders(), 25 | focus: ToggleButtonFocus = ToggleButtonDefaults.toggleButtonFocus(), 26 | shape: Shape = ToggleButtonDefaults.Shape, 27 | contentPadding: PaddingValues = ToggleButtonDefaults.ContentPadding, 28 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 29 | content: @Composable RowScope.() -> Unit 30 | ) { 31 | val backgroundColor by colors.backgroundColor(enabled, toggled, interactionSource) 32 | val contentColor by colors.contentColor(enabled, toggled, interactionSource) 33 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 34 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 35 | val borderStroke by border.border(enabled, toggled, interactionSource) 36 | Box( 37 | modifier = modifier 38 | .focusStroke( 39 | outerColor = focusOuterStroke, 40 | innerColor = focusInnerStroke, 41 | shape = shape 42 | ) 43 | .clip(shape) 44 | .background(backgroundColor) 45 | .toggleable( 46 | onValueChange = onToggle, 47 | value = toggled, 48 | indication = null, 49 | interactionSource = interactionSource, 50 | enabled = enabled, 51 | role = Role.Button 52 | ) 53 | .focusable( 54 | enabled = enabled, 55 | interactionSource = interactionSource 56 | ) 57 | .then(if (borderStroke != null) Modifier.border(borderStroke!!, shape) else Modifier) 58 | ) { 59 | CompositionLocalProvider( 60 | LocalContentColor provides contentColor, 61 | LocalTextStyle provides FluentTheme.typography.body 62 | ) { 63 | Row( 64 | modifier = Modifier 65 | .defaultMinSize( 66 | minWidth = ToggleButtonDefaults.MinWidth, 67 | minHeight = ToggleButtonDefaults.MinHeight 68 | ) 69 | .padding(contentPadding), 70 | verticalAlignment = Alignment.CenterVertically, 71 | horizontalArrangement = Arrangement.Center, 72 | content = content 73 | ) 74 | } 75 | } 76 | } 77 | 78 | object ToggleButtonDefaults { 79 | 80 | val MinWidth = 120.dp 81 | val MinHeight = 32.dp 82 | 83 | val ContentPadding = PaddingValues( 84 | start = 12.dp, 85 | end = 12.dp, 86 | top = 5.dp, 87 | bottom = 7.dp 88 | ) 89 | 90 | val Shape 91 | @Composable 92 | @ReadOnlyComposable 93 | get() = FluentTheme.shapes.small 94 | 95 | @Composable 96 | fun toggleButtonColors( 97 | backgroundColor: Color = FluentTheme.colorScheme.fillControlDefault, 98 | backgroundHoverColor: Color = FluentTheme.colorScheme.fillControlSecondary, 99 | backgroundPressedColor: Color = FluentTheme.colorScheme.fillControlTertiary, 100 | backgroundDisabledColor: Color = FluentTheme.colorScheme.fillControlDisabled, 101 | backgroundFocusedColor: Color = backgroundColor, 102 | toggledBackgroundColor: Color = FluentTheme.colorScheme.fillAccentDefault, 103 | toggledBackgroundHoverColor: Color = FluentTheme.colorScheme.fillAccentSecondary, 104 | toggledBackgroundPressedColor: Color = FluentTheme.colorScheme.fillAccentTertiary, 105 | toggledBackgroundDisabledColor: Color = FluentTheme.colorScheme.fillAccentDisabled, 106 | toggledBackgroundFocusedColor: Color = toggledBackgroundColor, 107 | contentColor: Color = FluentTheme.colorScheme.textPrimary, 108 | contentHoverColor: Color = FluentTheme.colorScheme.textPrimary, 109 | contentPressedColor: Color = FluentTheme.colorScheme.textSecondary, 110 | contentDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 111 | contentFocusedColor: Color = contentColor, 112 | toggledContentColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 113 | toggledContentHoverColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 114 | toggledContentPressedColor: Color = FluentTheme.colorScheme.textOnAccentSecondary, 115 | toggledContentDisabledColor: Color = FluentTheme.colorScheme.textOnAccentDisabled, 116 | toggledContentFocusedColor: Color = toggledContentColor, 117 | ): ToggleButtonColors { 118 | return DefaultToggleButtonColors( 119 | backgroundColor = backgroundColor, 120 | backgroundHoverColor = backgroundHoverColor, 121 | backgroundPressedColor = backgroundPressedColor, 122 | backgroundDisabledColor = backgroundDisabledColor, 123 | backgroundFocusedColor = backgroundFocusedColor, 124 | toggledBackgroundColor = toggledBackgroundColor, 125 | toggledBackgroundHoverColor = toggledBackgroundHoverColor, 126 | toggledBackgroundPressedColor = toggledBackgroundPressedColor, 127 | toggledBackgroundDisabledColor = toggledBackgroundDisabledColor, 128 | toggledBackgroundFocusedColor = toggledBackgroundFocusedColor, 129 | contentColor = contentColor, 130 | contentHoverColor = contentHoverColor, 131 | contentPressedColor = contentPressedColor, 132 | contentDisabledColor = contentDisabledColor, 133 | contentFocusedColor = contentFocusedColor, 134 | toggledContentColor = toggledContentColor, 135 | toggledContentHoverColor = toggledContentHoverColor, 136 | toggledContentPressedColor = toggledContentPressedColor, 137 | toggledContentDisabledColor = toggledContentDisabledColor, 138 | toggledContentFocusedColor = toggledContentFocusedColor 139 | ) 140 | } 141 | 142 | @Composable 143 | fun toggleButtonBorders( 144 | stroke: BorderStroke? = BorderStroke( 145 | width = 1.dp, 146 | brush = FluentTheme.borderElevation.control 147 | ), 148 | strokeHover: BorderStroke? = stroke, 149 | strokePressed: BorderStroke? = BorderStroke( 150 | width = 1.dp, 151 | color = FluentTheme.colorScheme.strokeControlDefault 152 | ), 153 | strokeDisabled: BorderStroke? = strokePressed, 154 | strokeFocused: BorderStroke? = stroke, 155 | toggledStroke: BorderStroke? = BorderStroke( 156 | width = 1.dp, 157 | brush = FluentTheme.borderElevation.accentControl 158 | ), 159 | toggledStrokeHover: BorderStroke? = toggledStroke, 160 | toggledStrokePressed: BorderStroke? = BorderStroke( 161 | width = 1.dp, 162 | color = FluentTheme.colorScheme.strokeControlOnAccentDefault 163 | ), 164 | toggledStrokeDisabled: BorderStroke? = null, 165 | toggledStrokeFocused: BorderStroke? = toggledStroke, 166 | ): ToggleButtonBorders { 167 | return DefaultToggleButtonBorders( 168 | stroke = stroke, 169 | strokeHover = strokeHover, 170 | strokePressed = strokePressed, 171 | strokeDisabled = strokeDisabled, 172 | strokeFocused = strokeFocused, 173 | toggledStroke = toggledStroke, 174 | toggledStrokeHover = toggledStrokeHover, 175 | toggledStrokePressed = toggledStrokePressed, 176 | toggledStrokeDisabled = toggledStrokeDisabled, 177 | toggledStrokeFocused = toggledStrokeFocused 178 | ) 179 | } 180 | 181 | @Composable 182 | fun toggleButtonFocus( 183 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 184 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 185 | ): ToggleButtonFocus { 186 | return DefaultToggleButtonFocus( 187 | innerStroke = innerStroke, 188 | outerStroke = outerStroke 189 | ) 190 | } 191 | 192 | } 193 | 194 | interface ToggleButtonColors { 195 | 196 | @Composable 197 | fun backgroundColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 198 | 199 | @Composable 200 | fun contentColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 201 | 202 | } 203 | 204 | interface ToggleButtonBorders { 205 | 206 | @Composable 207 | fun border(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 208 | 209 | } 210 | 211 | interface ToggleButtonFocus { 212 | 213 | @Composable 214 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 215 | 216 | @Composable 217 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 218 | 219 | } 220 | 221 | @Immutable 222 | data class DefaultToggleButtonColors( 223 | private val backgroundColor: Color, 224 | private val backgroundHoverColor: Color, 225 | private val backgroundPressedColor: Color, 226 | private val backgroundDisabledColor: Color, 227 | private val backgroundFocusedColor: Color, 228 | private val toggledBackgroundColor: Color, 229 | private val toggledBackgroundHoverColor: Color, 230 | private val toggledBackgroundPressedColor: Color, 231 | private val toggledBackgroundDisabledColor: Color, 232 | private val toggledBackgroundFocusedColor: Color, 233 | private val contentColor: Color, 234 | private val contentHoverColor: Color, 235 | private val contentPressedColor: Color, 236 | private val contentDisabledColor: Color, 237 | private val contentFocusedColor: Color, 238 | private val toggledContentColor: Color, 239 | private val toggledContentHoverColor: Color, 240 | private val toggledContentPressedColor: Color, 241 | private val toggledContentDisabledColor: Color, 242 | private val toggledContentFocusedColor: Color, 243 | ) : ToggleButtonColors { 244 | 245 | @Composable 246 | override fun backgroundColor( 247 | enabled: Boolean, 248 | toggled: Boolean, 249 | interactionSource: InteractionSource 250 | ): State { 251 | val interaction by interactionSource.collectInteractionAsState() 252 | val target = if (!enabled) { 253 | when (toggled) { 254 | true -> toggledBackgroundDisabledColor 255 | false -> backgroundDisabledColor 256 | } 257 | } else { 258 | when (interaction) { 259 | is PressInteraction.Press -> when (toggled) { 260 | true -> toggledBackgroundPressedColor 261 | false -> backgroundPressedColor 262 | } 263 | 264 | is HoverInteraction.Enter -> when (toggled) { 265 | true -> toggledBackgroundHoverColor 266 | false -> backgroundHoverColor 267 | } 268 | 269 | is FocusInteraction.Focus -> when (toggled) { 270 | true -> toggledBackgroundFocusedColor 271 | false -> backgroundFocusedColor 272 | } 273 | 274 | else -> when (toggled) { 275 | true -> toggledBackgroundColor 276 | false -> backgroundColor 277 | } 278 | } 279 | } 280 | return rememberUpdatedState(target) 281 | } 282 | 283 | @Composable 284 | override fun contentColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State { 285 | val interaction by interactionSource.collectInteractionAsState() 286 | val target = if (!enabled) { 287 | when (toggled) { 288 | true -> toggledContentDisabledColor 289 | false -> contentDisabledColor 290 | } 291 | } else { 292 | when (interaction) { 293 | is PressInteraction.Press -> when (toggled) { 294 | true -> toggledContentPressedColor 295 | false -> contentPressedColor 296 | } 297 | 298 | is HoverInteraction.Enter -> when (toggled) { 299 | true -> toggledContentHoverColor 300 | false -> contentHoverColor 301 | } 302 | 303 | is FocusInteraction.Focus -> when (toggled) { 304 | true -> toggledContentFocusedColor 305 | false -> contentFocusedColor 306 | } 307 | 308 | else -> when (toggled) { 309 | true -> toggledContentColor 310 | false -> contentColor 311 | } 312 | } 313 | } 314 | return rememberUpdatedState(target) 315 | } 316 | } 317 | 318 | @Immutable 319 | data class DefaultToggleButtonBorders( 320 | private val stroke: BorderStroke?, 321 | private val strokeHover: BorderStroke?, 322 | private val strokePressed: BorderStroke?, 323 | private val strokeDisabled: BorderStroke?, 324 | private val strokeFocused: BorderStroke?, 325 | private val toggledStroke: BorderStroke?, 326 | private val toggledStrokeHover: BorderStroke?, 327 | private val toggledStrokePressed: BorderStroke?, 328 | private val toggledStrokeDisabled: BorderStroke?, 329 | private val toggledStrokeFocused: BorderStroke?, 330 | ) : ToggleButtonBorders { 331 | 332 | @Composable 333 | override fun border( 334 | enabled: Boolean, 335 | toggled: Boolean, 336 | interactionSource: InteractionSource 337 | ): State { 338 | val interaction by interactionSource.collectInteractionAsState() 339 | 340 | val target = if (!enabled) { 341 | when (toggled) { 342 | true -> toggledStrokeDisabled 343 | false -> strokeDisabled 344 | } 345 | } else { 346 | when (interaction) { 347 | is PressInteraction.Press -> when (toggled) { 348 | true -> toggledStrokePressed 349 | false -> strokePressed 350 | } 351 | 352 | is HoverInteraction.Enter -> when (toggled) { 353 | true -> toggledStrokeHover 354 | false -> strokeHover 355 | } 356 | 357 | is FocusInteraction.Focus -> when (toggled) { 358 | true -> toggledStrokeFocused 359 | false -> strokeFocused 360 | } 361 | 362 | else -> when (toggled) { 363 | true -> toggledStroke 364 | false -> stroke 365 | } 366 | } 367 | } 368 | return rememberUpdatedState(target) 369 | } 370 | 371 | } 372 | 373 | @Immutable 374 | data class DefaultToggleButtonFocus( 375 | private val innerStroke: Color, 376 | private val outerStroke: Color, 377 | ) : ToggleButtonFocus { 378 | 379 | @Composable 380 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 381 | val isFocused by interactionSource.collectIsFocusedAsState() 382 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 383 | } 384 | 385 | @Composable 386 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 387 | val isFocused by interactionSource.collectIsFocusedAsState() 388 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 389 | } 390 | 391 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/Radio.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.core.animateDpAsState 2 | import androidx.compose.foundation.* 3 | import androidx.compose.foundation.interaction.* 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.selection.selectable 6 | import androidx.compose.foundation.shape.CircleShape 7 | import androidx.compose.runtime.* 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.clip 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.semantics.Role 13 | import androidx.compose.ui.unit.dp 14 | 15 | @Composable 16 | fun RadioGroupColumn( 17 | modifier: Modifier = Modifier, 18 | headerColor: Color = RadioGroupDefaults.HeaderColor, 19 | header: @Composable () -> Unit, 20 | content: @Composable () -> Unit, 21 | ) { 22 | RadioGroupImpl( 23 | modifier = modifier, 24 | headerColor = headerColor, 25 | header = header 26 | ) { 27 | Column( 28 | verticalArrangement = Arrangement.spacedBy(8.dp), 29 | horizontalAlignment = Alignment.CenterHorizontally 30 | ) { 31 | content() 32 | } 33 | } 34 | } 35 | 36 | @Composable 37 | fun RadioGroupRow( 38 | modifier: Modifier = Modifier, 39 | headerColor: Color = RadioGroupDefaults.HeaderColor, 40 | header: @Composable () -> Unit, 41 | content: @Composable () -> Unit, 42 | ) { 43 | RadioGroupImpl( 44 | modifier = modifier, 45 | headerColor = headerColor, 46 | header = header 47 | ) { 48 | Row( 49 | horizontalArrangement = Arrangement.spacedBy(8.dp), 50 | verticalAlignment = Alignment.CenterVertically 51 | ) { 52 | content() 53 | } 54 | } 55 | } 56 | 57 | @Composable 58 | private fun RadioGroupImpl( 59 | modifier: Modifier = Modifier, 60 | headerColor: Color = RadioGroupDefaults.HeaderColor, 61 | header: @Composable () -> Unit, 62 | layout: @Composable () -> Unit, 63 | ) { 64 | Column( 65 | modifier = modifier, 66 | verticalArrangement = Arrangement.spacedBy(7.dp), 67 | horizontalAlignment = Alignment.Start 68 | ) { 69 | Box(modifier = Modifier.padding(start = 3.dp, top = 1.dp)) { 70 | CompositionLocalProvider( 71 | LocalTextStyle provides FluentTheme.typography.body, 72 | LocalContentColor provides headerColor 73 | ) { 74 | header() 75 | } 76 | } 77 | layout() 78 | } 79 | } 80 | 81 | @Composable 82 | fun RadioButton( 83 | onSelect: () -> Unit, 84 | selected: Boolean, 85 | modifier: Modifier = Modifier, 86 | enabled: Boolean = true, 87 | colors: RadioButtonColors = RadioButtonDefaults.radioButtonColors(), 88 | border: RadioButtonBorders = RadioButtonDefaults.radioButtonBorders(), 89 | focus: RadioButtonFocus = RadioButtonDefaults.radioButtonFocus(), 90 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 91 | content: (@Composable () -> Unit)? = null, 92 | ) { 93 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 94 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 95 | val contentColor by colors.contentColor(enabled, interactionSource) 96 | 97 | val sharedModifiers = Modifier 98 | .selectable( 99 | onClick = onSelect, 100 | selected = selected, 101 | enabled = enabled, 102 | role = Role.RadioButton, 103 | indication = null, 104 | interactionSource = interactionSource, 105 | ) 106 | .focusable( 107 | enabled = enabled, 108 | interactionSource = interactionSource 109 | ) 110 | 111 | if (content == null) { 112 | RadioButtonImpl( 113 | modifier = modifier 114 | .focusStroke( 115 | outerColor = focusOuterStroke, 116 | innerColor = focusInnerStroke, 117 | shape = CircleShape 118 | ).then(sharedModifiers), 119 | selected = selected, 120 | enabled = enabled, 121 | colors = colors, 122 | border = border, 123 | interactionSource = interactionSource, 124 | ) 125 | } else { 126 | Row( 127 | modifier = modifier 128 | .focusStroke( 129 | outerColor = focusOuterStroke, 130 | innerColor = focusInnerStroke, 131 | shape = FluentTheme.shapes.medium 132 | ) 133 | .padding(start = 4.dp, end = 8.dp) 134 | .defaultMinSize( 135 | minWidth = RadioButtonDefaults.ContainerMinWidth, 136 | minHeight = RadioButtonDefaults.ContainerMinHeight 137 | ) 138 | .then(sharedModifiers), 139 | horizontalArrangement = Arrangement.spacedBy(8.dp), 140 | verticalAlignment = Alignment.CenterVertically 141 | ) { 142 | RadioButtonImpl( 143 | modifier = Modifier, 144 | selected = selected, 145 | enabled = enabled, 146 | colors = colors, 147 | border = border, 148 | interactionSource = interactionSource, 149 | ) 150 | CompositionLocalProvider( 151 | LocalContentColor provides contentColor, 152 | LocalTextStyle provides FluentTheme.typography.body 153 | ) { 154 | content() 155 | } 156 | } 157 | } 158 | } 159 | 160 | @Composable 161 | private fun RadioButtonImpl( 162 | modifier: Modifier, 163 | selected: Boolean, 164 | enabled: Boolean, 165 | colors: RadioButtonColors, 166 | border: RadioButtonBorders, 167 | interactionSource: MutableInteractionSource, 168 | ) { 169 | val containerColor by colors.containerColor(enabled, selected, interactionSource) 170 | val bulletColor by colors.bulletColor(enabled, selected, interactionSource) 171 | val borderStroke by border.border(enabled, selected, interactionSource) 172 | 173 | val interaction by interactionSource.collectInteractionAsState() 174 | 175 | val bulletSize by animateDpAsState( 176 | when (selected) { 177 | true -> when { 178 | enabled && interaction is PressInteraction.Press -> 6.dp 179 | enabled && interaction is HoverInteraction.Enter -> 10.dp 180 | else -> 8.dp 181 | } 182 | false -> when { 183 | enabled && interaction is PressInteraction.Press -> { 184 | when (FluentTheme.colorScheme.isDark) { 185 | true -> 8.dp 186 | else -> 6.dp 187 | } 188 | } 189 | else -> 0.dp 190 | } 191 | }, 192 | ) 193 | Box( 194 | modifier = modifier 195 | .clip(CircleShape) 196 | .background(containerColor) 197 | .defaultMinSize( 198 | minWidth = RadioButtonDefaults.BulletMinSize, 199 | minHeight = RadioButtonDefaults.BulletMinSize 200 | ) 201 | .then(if (borderStroke != null) Modifier.border(borderStroke!!, CircleShape) else Modifier), 202 | contentAlignment = Alignment.Center, 203 | ) { 204 | Box( 205 | modifier = Modifier 206 | .size(bulletSize) 207 | .clip(CircleShape) 208 | .background(bulletColor) 209 | ) 210 | } 211 | } 212 | 213 | object RadioGroupDefaults { 214 | 215 | val HeaderColor 216 | @Composable 217 | @ReadOnlyComposable 218 | get() = FluentTheme.colorScheme.textPrimary 219 | 220 | } 221 | 222 | object RadioButtonDefaults { 223 | 224 | val BulletMinSize = 20.dp 225 | 226 | val ContainerMinHeight = 32.dp 227 | val ContainerMinWidth = 109.dp 228 | 229 | @Composable 230 | fun radioButtonColors( 231 | containerColor: Color = FluentTheme.colorScheme.fillControlAltSecondary, 232 | containerHoverColor: Color = FluentTheme.colorScheme.fillControlAltTertiary, 233 | containerPressedColor: Color = FluentTheme.colorScheme.fillControlAltQuarternary, 234 | containerDisabledColor: Color = FluentTheme.colorScheme.fillControlAltDisabled, 235 | containerFocusedColor: Color = containerColor, 236 | selectedContainerColor: Color = FluentTheme.colorScheme.fillAccentDefault, 237 | selectedContainerHoverColor: Color = FluentTheme.colorScheme.fillAccentSecondary, 238 | selectedContainerPressedColor: Color = FluentTheme.colorScheme.fillAccentTertiary, 239 | selectedContainerDisabledColor: Color = FluentTheme.colorScheme.fillAccentDisabled, 240 | selectedContainerFocusedColor: Color = selectedContainerColor, 241 | bulletColor: Color = FluentTheme.colorScheme.textSecondary, 242 | bulletHoverColor: Color = FluentTheme.colorScheme.textSecondary, 243 | bulletPressedColor: Color = FluentTheme.colorScheme.textSecondary, 244 | bulletDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 245 | bulletFocusedColor: Color = bulletColor, 246 | selectedThumbColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 247 | selectedThumbHoverColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 248 | selectedThumbPressedColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 249 | selectedThumbDisabledColor: Color = FluentTheme.colorScheme.textOnAccentDisabled, 250 | selectedThumbFocusedColor: Color = selectedThumbColor, 251 | contentColor: Color = FluentTheme.colorScheme.textPrimary, 252 | contentHoverColor: Color = FluentTheme.colorScheme.textPrimary, 253 | contentPressedColor: Color = FluentTheme.colorScheme.textPrimary, 254 | contentDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 255 | contentFocusedColor: Color = FluentTheme.colorScheme.textPrimary, 256 | ): RadioButtonColors { 257 | return DefaultRadioButtonColors( 258 | containerColor = containerColor, 259 | containerHoverColor = containerHoverColor, 260 | containerPressedColor = containerPressedColor, 261 | containerDisabledColor = containerDisabledColor, 262 | containerFocusedColor = containerFocusedColor, 263 | selectedContainerColor = selectedContainerColor, 264 | selectedContainerHoverColor = selectedContainerHoverColor, 265 | selectedContainerPressedColor = selectedContainerPressedColor, 266 | selectedContainerDisabledColor = selectedContainerDisabledColor, 267 | selectedContainerFocusedColor = selectedContainerFocusedColor, 268 | bulletColor = bulletColor, 269 | bulletHoverColor = bulletHoverColor, 270 | bulletPressedColor = bulletPressedColor, 271 | bulletDisabledColor = bulletDisabledColor, 272 | bulletFocusedColor = bulletFocusedColor, 273 | selectedThumbColor = selectedThumbColor, 274 | selectedThumbHoverColor = selectedThumbHoverColor, 275 | selectedThumbPressedColor = selectedThumbPressedColor, 276 | selectedThumbDisabledColor = selectedThumbDisabledColor, 277 | selectedThumbFocusedColor = selectedThumbFocusedColor, 278 | contentColor = contentColor, 279 | contentHoverColor = contentHoverColor, 280 | contentPressedColor = contentPressedColor, 281 | contentDisabledColor = contentDisabledColor, 282 | contentFocusedColor = contentFocusedColor 283 | ) 284 | } 285 | 286 | @Composable 287 | fun radioButtonBorders( 288 | stroke: BorderStroke? = BorderStroke( 289 | width = 1.dp, 290 | color = FluentTheme.colorScheme.strokeControlStrong 291 | ), 292 | selectedStroke: BorderStroke? = BorderStroke( 293 | width = 1.dp, 294 | color = FluentTheme.colorScheme.fillAccentTertiary 295 | ), 296 | strokeHover: BorderStroke? = stroke, 297 | selectedStrokeHover: BorderStroke? = selectedStroke, 298 | strokePressed: BorderStroke? = stroke, 299 | selectedStrokePressed: BorderStroke? = selectedStroke, 300 | strokeDisabled: BorderStroke? = BorderStroke( 301 | width = 1.dp, 302 | color = FluentTheme.colorScheme.strokeControlStrongDisabled 303 | ), 304 | selectedStrokeDisabled: BorderStroke? = null, 305 | strokeFocused: BorderStroke? = stroke, 306 | selectedStrokeFocused: BorderStroke? = selectedStroke, 307 | ): RadioButtonBorders { 308 | return DefaultRadioButtonBorders( 309 | stroke = stroke, 310 | selectedStroke = selectedStroke, 311 | strokeHover = strokeHover, 312 | selectedStrokeHover = selectedStrokeHover, 313 | strokePressed = strokePressed, 314 | selectedStrokePressed = selectedStrokePressed, 315 | strokeDisabled = strokeDisabled, 316 | selectedStrokeDisabled = selectedStrokeDisabled, 317 | strokeFocused = strokeFocused, 318 | selectedStrokeFocused = selectedStrokeFocused 319 | ) 320 | } 321 | 322 | @Composable 323 | fun radioButtonFocus( 324 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 325 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 326 | ): RadioButtonFocus { 327 | return DefaultRadioButtonFocus( 328 | innerStroke = innerStroke, 329 | outerStroke = outerStroke 330 | ) 331 | } 332 | 333 | } 334 | 335 | interface RadioButtonColors { 336 | 337 | @Composable 338 | fun containerColor(enabled: Boolean, selected: Boolean, interactionSource: InteractionSource): State 339 | 340 | @Composable 341 | fun bulletColor(enabled: Boolean, selected: Boolean, interactionSource: InteractionSource): State 342 | 343 | @Composable 344 | fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State 345 | 346 | } 347 | 348 | interface RadioButtonBorders { 349 | 350 | @Composable 351 | fun border(enabled: Boolean, selected: Boolean, interactionSource: InteractionSource): State 352 | 353 | } 354 | 355 | interface RadioButtonFocus { 356 | 357 | @Composable 358 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 359 | 360 | @Composable 361 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 362 | 363 | } 364 | 365 | @Immutable 366 | data class DefaultRadioButtonColors( 367 | private val containerColor: Color, 368 | private val containerHoverColor: Color, 369 | private val containerPressedColor: Color, 370 | private val containerDisabledColor: Color, 371 | private val containerFocusedColor: Color, 372 | private val selectedContainerColor: Color, 373 | private val selectedContainerHoverColor: Color, 374 | private val selectedContainerPressedColor: Color, 375 | private val selectedContainerDisabledColor: Color, 376 | private val selectedContainerFocusedColor: Color, 377 | private val bulletColor: Color, 378 | private val bulletHoverColor: Color, 379 | private val bulletPressedColor: Color, 380 | private val bulletDisabledColor: Color, 381 | private val bulletFocusedColor: Color, 382 | private val selectedThumbColor: Color, 383 | private val selectedThumbHoverColor: Color, 384 | private val selectedThumbPressedColor: Color, 385 | private val selectedThumbDisabledColor: Color, 386 | private val selectedThumbFocusedColor: Color, 387 | private val contentColor: Color, 388 | private val contentHoverColor: Color, 389 | private val contentPressedColor: Color, 390 | private val contentDisabledColor: Color, 391 | private val contentFocusedColor: Color, 392 | ) : RadioButtonColors { 393 | 394 | @Composable 395 | override fun containerColor( 396 | enabled: Boolean, 397 | selected: Boolean, 398 | interactionSource: InteractionSource 399 | ): State { 400 | val interaction by interactionSource.collectInteractionAsState() 401 | val target = if (!enabled) { 402 | when (selected) { 403 | true -> selectedContainerDisabledColor 404 | false -> containerDisabledColor 405 | } 406 | } else { 407 | when (interaction) { 408 | is PressInteraction.Press -> when (selected) { 409 | true -> selectedContainerPressedColor 410 | false -> containerPressedColor 411 | } 412 | 413 | is HoverInteraction.Enter -> when (selected) { 414 | true -> selectedContainerHoverColor 415 | false -> containerHoverColor 416 | } 417 | 418 | is FocusInteraction.Focus -> when (selected) { 419 | true -> selectedContainerFocusedColor 420 | false -> containerFocusedColor 421 | } 422 | 423 | else -> when (selected) { 424 | true -> selectedContainerColor 425 | false -> containerColor 426 | } 427 | } 428 | } 429 | return rememberUpdatedState(target) 430 | } 431 | 432 | @Composable 433 | override fun bulletColor(enabled: Boolean, selected: Boolean, interactionSource: InteractionSource): State { 434 | val interaction by interactionSource.collectInteractionAsState() 435 | val target = if (!enabled) { 436 | when (selected) { 437 | true -> selectedThumbDisabledColor 438 | false -> bulletDisabledColor 439 | } 440 | } else { 441 | when (interaction) { 442 | is PressInteraction.Press -> when (selected) { 443 | true -> selectedThumbPressedColor 444 | false -> bulletPressedColor 445 | } 446 | 447 | is HoverInteraction.Enter -> when (selected) { 448 | true -> selectedThumbHoverColor 449 | false -> bulletHoverColor 450 | } 451 | 452 | is FocusInteraction.Focus -> when (selected) { 453 | true -> selectedThumbFocusedColor 454 | false -> bulletFocusedColor 455 | } 456 | 457 | else -> when (selected) { 458 | true -> selectedThumbColor 459 | false -> bulletColor 460 | } 461 | } 462 | } 463 | return rememberUpdatedState(target) 464 | } 465 | 466 | @Composable 467 | override fun contentColor(enabled: Boolean, interactionSource: InteractionSource): State { 468 | val interaction by interactionSource.collectInteractionAsState() 469 | val target = if (!enabled) { 470 | contentDisabledColor 471 | } else { 472 | when (interaction) { 473 | is PressInteraction.Press -> contentPressedColor 474 | is HoverInteraction.Enter -> contentHoverColor 475 | is FocusInteraction.Focus -> contentFocusedColor 476 | else -> contentColor 477 | } 478 | } 479 | return rememberUpdatedState(target) 480 | } 481 | } 482 | 483 | @Immutable 484 | data class DefaultRadioButtonBorders( 485 | private val stroke: BorderStroke?, 486 | private val selectedStroke: BorderStroke?, 487 | private val strokeHover: BorderStroke?, 488 | private val selectedStrokeHover: BorderStroke?, 489 | private val strokePressed: BorderStroke?, 490 | private val selectedStrokePressed: BorderStroke?, 491 | private val strokeDisabled: BorderStroke?, 492 | private val selectedStrokeDisabled: BorderStroke?, 493 | private val strokeFocused: BorderStroke?, 494 | private val selectedStrokeFocused: BorderStroke?, 495 | ) : RadioButtonBorders { 496 | 497 | @Composable 498 | override fun border( 499 | enabled: Boolean, 500 | selected: Boolean, 501 | interactionSource: InteractionSource 502 | ): State { 503 | val interaction by interactionSource.collectInteractionAsState() 504 | 505 | val target = if (!enabled) { 506 | when (selected) { 507 | true -> selectedStrokeDisabled 508 | false -> strokeDisabled 509 | } 510 | } else { 511 | when (interaction) { 512 | is PressInteraction.Press -> when (selected) { 513 | true -> selectedStrokePressed 514 | false -> strokePressed 515 | } 516 | 517 | is HoverInteraction.Enter -> when (selected) { 518 | true -> selectedStrokeHover 519 | false -> strokeHover 520 | } 521 | 522 | is FocusInteraction.Focus -> when (selected) { 523 | true -> selectedStrokeFocused 524 | false -> strokeFocused 525 | } 526 | 527 | else -> when (selected) { 528 | true -> selectedStroke 529 | false -> stroke 530 | } 531 | } 532 | } 533 | return rememberUpdatedState(target) 534 | } 535 | 536 | } 537 | 538 | @Immutable 539 | data class DefaultRadioButtonFocus( 540 | private val innerStroke: Color, 541 | private val outerStroke: Color, 542 | ) : RadioButtonFocus { 543 | 544 | @Composable 545 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 546 | val isFocused by interactionSource.collectIsFocusedAsState() 547 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 548 | } 549 | 550 | @Composable 551 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 552 | val isFocused by interactionSource.collectIsFocusedAsState() 553 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 554 | } 555 | 556 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/ToggleSwitch.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.core.Animatable 2 | import androidx.compose.animation.core.AnimationVector 3 | import androidx.compose.animation.core.TwoWayConverter 4 | import androidx.compose.foundation.BorderStroke 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.border 7 | import androidx.compose.foundation.focusable 8 | import androidx.compose.foundation.gestures.Orientation 9 | import androidx.compose.foundation.gestures.draggable 10 | import androidx.compose.foundation.gestures.rememberDraggableState 11 | import androidx.compose.foundation.interaction.* 12 | import androidx.compose.foundation.layout.* 13 | import androidx.compose.foundation.selection.toggleable 14 | import androidx.compose.foundation.shape.CircleShape 15 | import androidx.compose.runtime.* 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.BiasAlignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.draw.clip 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.layout.onGloballyPositioned 22 | import androidx.compose.ui.layout.positionInParent 23 | import androidx.compose.ui.semantics.Role 24 | import androidx.compose.ui.unit.dp 25 | import kotlinx.coroutines.launch 26 | 27 | @Composable 28 | fun ToggleSwitch( 29 | onToggle: (Boolean) -> Unit, 30 | toggled: Boolean, 31 | modifier: Modifier = Modifier, 32 | enabled: Boolean = true, 33 | colors: ToggleSwitchColors = ToggleSwitchDefaults.toggleSwitchColors(), 34 | border: ToggleSwitchBorders = ToggleSwitchDefaults.toggleSwitchBorders(), 35 | focus: ToggleSwitchFocus = ToggleSwitchDefaults.toggleSwitchFocus(), 36 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 37 | header: (@Composable () -> Unit)? = null, 38 | textBefore: (@Composable () -> Unit)? = null, 39 | textAfter: (@Composable () -> Unit)? = null, 40 | ) { 41 | val focusOuterStroke by focus.outerStroke(enabled, interactionSource) 42 | val focusInnerStroke by focus.innerStroke(enabled, interactionSource) 43 | val textColor by colors.textColor(enabled, interactionSource) 44 | 45 | //TODO use Swipeable or something like that in the future 46 | val thumbAlignment = remember { 47 | Animatable( 48 | initialValue = BiasAlignment(0f, 0f), 49 | typeConverter = TwoWayConverter( 50 | convertToVector = { 51 | AnimationVector( 52 | it.horizontalBias 53 | ) 54 | }, 55 | convertFromVector = { 56 | BiasAlignment(it.value, 0f) 57 | } 58 | ) 59 | ) 60 | } 61 | 62 | LaunchedEffect(toggled) { 63 | thumbAlignment.animateTo( 64 | (if (toggled) Alignment.CenterEnd else Alignment.CenterStart) as BiasAlignment 65 | ) 66 | } 67 | 68 | SideEffect { 69 | thumbAlignment.updateBounds(lowerBound = BiasAlignment(-1f, 0f), upperBound = BiasAlignment(1f, 0f)) 70 | } 71 | 72 | val coroutineScope = rememberCoroutineScope() 73 | 74 | val sharedModifiers = Modifier 75 | .toggleable( 76 | onValueChange = onToggle, 77 | value = toggled, 78 | enabled = enabled, 79 | role = Role.Switch, 80 | indication = null, 81 | interactionSource = interactionSource, 82 | ) 83 | .focusable( 84 | enabled = enabled, 85 | interactionSource = interactionSource 86 | ) 87 | .draggable( 88 | state = rememberDraggableState { delta -> 89 | coroutineScope.launch { 90 | thumbAlignment.snapTo( 91 | BiasAlignment( 92 | horizontalBias = thumbAlignment.value.horizontalBias + delta, 93 | verticalBias = thumbAlignment.value.verticalBias 94 | ) 95 | ) 96 | } 97 | }, 98 | orientation = Orientation.Horizontal, 99 | onDragStopped = { 100 | onToggle(thumbAlignment.value.horizontalBias >= 0f) 101 | }, 102 | interactionSource = interactionSource, 103 | ) 104 | 105 | if (header == null && textBefore == null && textAfter == null) { 106 | ToggleSwitchImpl( 107 | modifier = modifier 108 | .focusStroke( 109 | outerColor = focusOuterStroke, 110 | innerColor = focusInnerStroke, 111 | shape = CircleShape 112 | ).then(sharedModifiers), 113 | toggled = toggled, 114 | enabled = enabled, 115 | colors = colors, 116 | border = border, 117 | interactionSource = interactionSource, 118 | thumbAlignment = thumbAlignment.value 119 | ) 120 | } else { 121 | Column( 122 | modifier = modifier, 123 | verticalArrangement = Arrangement.spacedBy(7.dp) 124 | ) { 125 | var headerOffset by remember { mutableStateOf(0.dp) } 126 | if (header != null) { 127 | CompositionLocalProvider( 128 | LocalTextStyle provides FluentTheme.typography.body, 129 | LocalContentColor provides FluentTheme.colorScheme.textPrimary 130 | ) { 131 | Box(modifier = Modifier.padding(horizontal = 4.dp).offset(x = headerOffset)) { 132 | header() 133 | } 134 | } 135 | } 136 | Row( 137 | modifier = Modifier 138 | .focusStroke( 139 | outerColor = focusOuterStroke, 140 | innerColor = focusInnerStroke, 141 | shape = FluentTheme.shapes.medium 142 | ) 143 | .padding(horizontal = 4.dp).then(sharedModifiers), 144 | horizontalArrangement = Arrangement.spacedBy(12.dp), 145 | verticalAlignment = Alignment.CenterVertically 146 | ) { 147 | if (textBefore != null) { 148 | Box(modifier = Modifier.padding(start = 6.dp, top = 5.dp, bottom = 7.dp)) { 149 | CompositionLocalProvider( 150 | LocalTextStyle provides FluentTheme.typography.body, 151 | LocalContentColor provides textColor 152 | ) { 153 | textBefore() 154 | } 155 | } 156 | } 157 | ToggleSwitchImpl( 158 | modifier = modifier.onGloballyPositioned { 159 | headerOffset = it.positionInParent().x.dp 160 | }, 161 | toggled = toggled, 162 | enabled = enabled, 163 | colors = colors, 164 | border = border, 165 | interactionSource = interactionSource, 166 | thumbAlignment = thumbAlignment.value 167 | ) 168 | if (textAfter != null) { 169 | Box(modifier = Modifier.padding(top = 5.dp, bottom = 7.dp, end = 6.dp)) { 170 | CompositionLocalProvider( 171 | LocalTextStyle provides FluentTheme.typography.body, 172 | LocalContentColor provides textColor 173 | ) { 174 | textAfter() 175 | } 176 | } 177 | } 178 | } 179 | } 180 | } 181 | } 182 | 183 | @Composable 184 | private fun ToggleSwitchImpl( 185 | modifier: Modifier, 186 | toggled: Boolean, 187 | enabled: Boolean, 188 | colors: ToggleSwitchColors, 189 | border: ToggleSwitchBorders, 190 | thumbAlignment: BiasAlignment, 191 | interactionSource: MutableInteractionSource, 192 | ) { 193 | val containerColor by colors.containerColor(enabled, toggled, interactionSource) 194 | val thumbColor by colors.thumbColor(enabled, toggled, interactionSource) 195 | val borderStroke by border.border(enabled, toggled, interactionSource) 196 | 197 | val interaction by interactionSource.collectInteractionAsState() 198 | 199 | val thumbWidth = when { 200 | enabled && (interaction is PressInteraction.Press || interaction is DragInteraction.Start) -> 17.dp 201 | enabled && interaction is HoverInteraction.Enter -> 14.dp 202 | else -> 12.dp 203 | } 204 | val thumbHeight = when { 205 | enabled && (interaction is PressInteraction.Press || interaction is HoverInteraction.Enter || interaction is DragInteraction.Start) -> 14.dp 206 | else -> 12.dp 207 | } 208 | 209 | Box( 210 | modifier = modifier 211 | .clip(CircleShape) 212 | .background(containerColor) 213 | .defaultMinSize( 214 | minWidth = ToggleSwitchDefaults.MinWidth, 215 | minHeight = ToggleSwitchDefaults.MinHeight 216 | ) 217 | .then(if (borderStroke != null) Modifier.border(borderStroke!!, CircleShape) else Modifier) 218 | ) { 219 | val padding = when { 220 | enabled && (interaction is PressInteraction.Press || interaction is HoverInteraction.Enter || interaction is DragInteraction.Start) -> 2.dp 221 | else -> 4.dp 222 | } 223 | Box( 224 | modifier = Modifier 225 | .padding(padding) 226 | .align(thumbAlignment) 227 | .clip(CircleShape) 228 | .background(thumbColor) 229 | .width(thumbWidth) 230 | .height(thumbHeight) 231 | ) 232 | } 233 | } 234 | 235 | 236 | object ToggleSwitchDefaults { 237 | 238 | val MinWidth = 40.dp 239 | val MinHeight = 20.dp 240 | 241 | @Composable 242 | fun toggleSwitchColors( 243 | containerColor: Color = FluentTheme.colorScheme.fillControlAltSecondary, 244 | containerHoverColor: Color = FluentTheme.colorScheme.fillControlAltTertiary, 245 | containerPressedColor: Color = FluentTheme.colorScheme.fillControlAltQuarternary, 246 | containerDisabledColor: Color = FluentTheme.colorScheme.fillControlAltDisabled, 247 | containerFocusedColor: Color = containerColor, 248 | toggledContainerColor: Color = FluentTheme.colorScheme.fillAccentDefault, 249 | toggledContainerHoverColor: Color = FluentTheme.colorScheme.fillAccentSecondary, 250 | toggledContainerPressedColor: Color = FluentTheme.colorScheme.fillAccentTertiary, 251 | toggledContainerDisabledColor: Color = FluentTheme.colorScheme.fillAccentDisabled, 252 | toggledContainerFocusedColor: Color = toggledContainerColor, 253 | thumbColor: Color = FluentTheme.colorScheme.textSecondary, 254 | thumbHoverColor: Color = FluentTheme.colorScheme.textSecondary, 255 | thumbPressedColor: Color = FluentTheme.colorScheme.textSecondary, 256 | thumbDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 257 | thumbFocusedColor: Color = thumbColor, 258 | toggledThumbColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 259 | toggledThumbHoverColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 260 | toggledThumbPressedColor: Color = FluentTheme.colorScheme.textOnAccentPrimary, 261 | toggledThumbDisabledColor: Color = FluentTheme.colorScheme.textOnAccentDisabled, 262 | toggledThumbFocusedColor: Color = toggledThumbColor, 263 | textColor: Color = FluentTheme.colorScheme.textPrimary, 264 | textHoverColor: Color = FluentTheme.colorScheme.textPrimary, 265 | textPressedColor: Color = FluentTheme.colorScheme.textPrimary, 266 | textDisabledColor: Color = FluentTheme.colorScheme.textDisabled, 267 | textFocusedColor: Color = FluentTheme.colorScheme.textPrimary, 268 | ): ToggleSwitchColors { 269 | return DefaultToggleSwitchColors( 270 | containerColor = containerColor, 271 | containerHoverColor = containerHoverColor, 272 | containerPressedColor = containerPressedColor, 273 | containerDisabledColor = containerDisabledColor, 274 | containerFocusedColor = containerFocusedColor, 275 | toggledContainerColor = toggledContainerColor, 276 | toggledContainerHoverColor = toggledContainerHoverColor, 277 | toggledContainerPressedColor = toggledContainerPressedColor, 278 | toggledContainerDisabledColor = toggledContainerDisabledColor, 279 | toggledContainerFocusedColor = toggledContainerFocusedColor, 280 | thumbColor = thumbColor, 281 | thumbHoverColor = thumbHoverColor, 282 | thumbPressedColor = thumbPressedColor, 283 | thumbDisabledColor = thumbDisabledColor, 284 | thumbFocusedColor = thumbFocusedColor, 285 | toggledThumbColor = toggledThumbColor, 286 | toggledThumbHoverColor = toggledThumbHoverColor, 287 | toggledThumbPressedColor = toggledThumbPressedColor, 288 | toggledThumbDisabledColor = toggledThumbDisabledColor, 289 | toggledThumbFocusedColor = toggledThumbFocusedColor, 290 | textColor = textColor, 291 | textHoverColor = textHoverColor, 292 | textPressedColor = textPressedColor, 293 | textDisabledColor = textDisabledColor, 294 | textFocusedColor = textFocusedColor 295 | ) 296 | } 297 | 298 | @Composable 299 | fun toggleSwitchBorders( 300 | stroke: BorderStroke? = BorderStroke( 301 | width = 1.dp, 302 | color = FluentTheme.colorScheme.strokeControlStrong 303 | ), 304 | toggledStroke: BorderStroke? = BorderStroke( 305 | width = 1.dp, 306 | color = FluentTheme.colorScheme.fillAccentTertiary 307 | ), 308 | strokeHover: BorderStroke? = stroke, 309 | toggledStrokeHover: BorderStroke? = toggledStroke, 310 | strokePressed: BorderStroke? = stroke, 311 | toggledStrokePressed: BorderStroke? = toggledStroke, 312 | strokeDisabled: BorderStroke? = BorderStroke( 313 | width = 1.dp, 314 | color = FluentTheme.colorScheme.strokeControlStrongDisabled 315 | ), 316 | toggledStrokeDisabled: BorderStroke? = null, 317 | strokeFocused: BorderStroke? = stroke, 318 | toggledStrokeFocused: BorderStroke? = toggledStroke, 319 | ): ToggleSwitchBorders { 320 | return DefaultToggleSwitchBorders( 321 | stroke = stroke, 322 | toggledStroke = toggledStroke, 323 | strokeHover = strokeHover, 324 | toggledStrokeHover = toggledStrokeHover, 325 | strokePressed = strokePressed, 326 | toggledStrokePressed = toggledStrokePressed, 327 | strokeDisabled = strokeDisabled, 328 | toggledStrokeDisabled = toggledStrokeDisabled, 329 | strokeFocused = strokeFocused, 330 | toggledStrokeFocused = toggledStrokeFocused 331 | ) 332 | } 333 | 334 | @Composable 335 | fun toggleSwitchFocus( 336 | innerStroke: Color = FluentTheme.colorScheme.strokeFocusInner, 337 | outerStroke: Color = FluentTheme.colorScheme.strokeFocusOuter 338 | ): ToggleSwitchFocus { 339 | return DefaultToggleSwitchFocus( 340 | innerStroke = innerStroke, 341 | outerStroke = outerStroke 342 | ) 343 | } 344 | 345 | } 346 | 347 | interface ToggleSwitchColors { 348 | 349 | @Composable 350 | fun containerColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 351 | 352 | @Composable 353 | fun thumbColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 354 | 355 | @Composable 356 | fun textColor(enabled: Boolean, interactionSource: InteractionSource): State 357 | 358 | } 359 | 360 | interface ToggleSwitchBorders { 361 | 362 | @Composable 363 | fun border(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State 364 | 365 | } 366 | 367 | interface ToggleSwitchFocus { 368 | 369 | @Composable 370 | fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State 371 | 372 | @Composable 373 | fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State 374 | 375 | } 376 | 377 | @Immutable 378 | data class DefaultToggleSwitchColors( 379 | private val containerColor: Color, 380 | private val containerHoverColor: Color, 381 | private val containerPressedColor: Color, 382 | private val containerDisabledColor: Color, 383 | private val containerFocusedColor: Color, 384 | private val toggledContainerColor: Color, 385 | private val toggledContainerHoverColor: Color, 386 | private val toggledContainerPressedColor: Color, 387 | private val toggledContainerDisabledColor: Color, 388 | private val toggledContainerFocusedColor: Color, 389 | private val thumbColor: Color, 390 | private val thumbHoverColor: Color, 391 | private val thumbPressedColor: Color, 392 | private val thumbDisabledColor: Color, 393 | private val thumbFocusedColor: Color, 394 | private val toggledThumbColor: Color, 395 | private val toggledThumbHoverColor: Color, 396 | private val toggledThumbPressedColor: Color, 397 | private val toggledThumbDisabledColor: Color, 398 | private val toggledThumbFocusedColor: Color, 399 | private val textColor: Color, 400 | private val textHoverColor: Color, 401 | private val textPressedColor: Color, 402 | private val textDisabledColor: Color, 403 | private val textFocusedColor: Color, 404 | ) : ToggleSwitchColors { 405 | 406 | @Composable 407 | override fun containerColor( 408 | enabled: Boolean, 409 | toggled: Boolean, 410 | interactionSource: InteractionSource 411 | ): State { 412 | val interaction by interactionSource.collectInteractionAsState() 413 | val target = if (!enabled) { 414 | when (toggled) { 415 | true -> toggledContainerDisabledColor 416 | false -> containerDisabledColor 417 | } 418 | } else { 419 | when (interaction) { 420 | is PressInteraction.Press -> when (toggled) { 421 | true -> toggledContainerPressedColor 422 | false -> containerPressedColor 423 | } 424 | 425 | is HoverInteraction.Enter -> when (toggled) { 426 | true -> toggledContainerHoverColor 427 | false -> containerHoverColor 428 | } 429 | 430 | is FocusInteraction.Focus -> when (toggled) { 431 | true -> toggledContainerFocusedColor 432 | false -> containerFocusedColor 433 | } 434 | 435 | else -> when (toggled) { 436 | true -> toggledContainerColor 437 | false -> containerColor 438 | } 439 | } 440 | } 441 | return rememberUpdatedState(target) 442 | } 443 | 444 | @Composable 445 | override fun thumbColor(enabled: Boolean, toggled: Boolean, interactionSource: InteractionSource): State { 446 | val interaction by interactionSource.collectInteractionAsState() 447 | val target = if (!enabled) { 448 | when (toggled) { 449 | true -> toggledThumbDisabledColor 450 | false -> thumbDisabledColor 451 | } 452 | } else { 453 | when (interaction) { 454 | is PressInteraction.Press -> when (toggled) { 455 | true -> toggledThumbPressedColor 456 | false -> thumbPressedColor 457 | } 458 | 459 | is HoverInteraction.Enter -> when (toggled) { 460 | true -> toggledThumbHoverColor 461 | false -> thumbHoverColor 462 | } 463 | 464 | is FocusInteraction.Focus -> when (toggled) { 465 | true -> toggledThumbFocusedColor 466 | false -> thumbFocusedColor 467 | } 468 | 469 | else -> when (toggled) { 470 | true -> toggledThumbColor 471 | false -> thumbColor 472 | } 473 | } 474 | } 475 | return rememberUpdatedState(target) 476 | } 477 | 478 | @Composable 479 | override fun textColor(enabled: Boolean, interactionSource: InteractionSource): State { 480 | val interaction by interactionSource.collectInteractionAsState() 481 | val target = if (!enabled) { 482 | textDisabledColor 483 | } else { 484 | when (interaction) { 485 | is PressInteraction.Press -> textPressedColor 486 | is HoverInteraction.Enter -> textHoverColor 487 | is FocusInteraction.Focus -> textFocusedColor 488 | else -> textColor 489 | } 490 | } 491 | return rememberUpdatedState(target) 492 | } 493 | } 494 | 495 | @Immutable 496 | data class DefaultToggleSwitchBorders( 497 | private val stroke: BorderStroke?, 498 | private val toggledStroke: BorderStroke?, 499 | private val strokeHover: BorderStroke?, 500 | private val toggledStrokeHover: BorderStroke?, 501 | private val strokePressed: BorderStroke?, 502 | private val toggledStrokePressed: BorderStroke?, 503 | private val strokeDisabled: BorderStroke?, 504 | private val toggledStrokeDisabled: BorderStroke?, 505 | private val strokeFocused: BorderStroke?, 506 | private val toggledStrokeFocused: BorderStroke?, 507 | ) : ToggleSwitchBorders { 508 | 509 | @Composable 510 | override fun border( 511 | enabled: Boolean, 512 | toggled: Boolean, 513 | interactionSource: InteractionSource 514 | ): State { 515 | val interaction by interactionSource.collectInteractionAsState() 516 | 517 | val target = if (!enabled) { 518 | when (toggled) { 519 | true -> toggledStrokeDisabled 520 | false -> strokeDisabled 521 | } 522 | } else { 523 | when (interaction) { 524 | is PressInteraction.Press -> when (toggled) { 525 | true -> toggledStrokePressed 526 | false -> strokePressed 527 | } 528 | 529 | is HoverInteraction.Enter -> when (toggled) { 530 | true -> toggledStrokeHover 531 | false -> strokeHover 532 | } 533 | 534 | is FocusInteraction.Focus -> when (toggled) { 535 | true -> toggledStrokeFocused 536 | false -> strokeFocused 537 | } 538 | 539 | else -> when (toggled) { 540 | true -> toggledStroke 541 | false -> stroke 542 | } 543 | } 544 | } 545 | return rememberUpdatedState(target) 546 | } 547 | 548 | } 549 | 550 | @Immutable 551 | data class DefaultToggleSwitchFocus( 552 | private val innerStroke: Color, 553 | private val outerStroke: Color, 554 | ) : ToggleSwitchFocus { 555 | 556 | @Composable 557 | override fun innerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 558 | val isFocused by interactionSource.collectIsFocusedAsState() 559 | return rememberUpdatedState(if (isFocused && enabled) innerStroke else Color.Transparent) 560 | } 561 | 562 | @Composable 563 | override fun outerStroke(enabled: Boolean, interactionSource: InteractionSource): State { 564 | val isFocused by interactionSource.collectIsFocusedAsState() 565 | return rememberUpdatedState(if (isFocused && enabled) outerStroke else Color.Transparent) 566 | } 567 | 568 | } -------------------------------------------------------------------------------- /lib/src/commonMain/kotlin/ColorScheme.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.* 2 | import androidx.compose.ui.graphics.Color 3 | 4 | fun lightColorScheme( 5 | textPrimary: Color = Color(0xE4000000), 6 | textSecondary: Color = Color(0x9B000000), 7 | textTertiary: Color = Color(0x72000000), 8 | textDisabled: Color = Color(0x5C000000), 9 | textAccentPrimary: Color = Color(0xFF003E92), 10 | textAccentSecondary: Color = Color(0xFF001A68), 11 | textAccentTertiary: Color = Color(0xFF005FB8), 12 | textAccentDisabled: Color = Color(0x5C000000), 13 | textOnAccentPrimary: Color = Color(0xFFFFFFFF), 14 | textOnAccentSecondary: Color = Color(0xB2FFFFFF), 15 | textOnAccentDisabled: Color = Color(0xFFFFFFFF), 16 | textOnAccentSelected: Color = Color(0xFFFFFFFF), 17 | fillControlDefault: Color = Color(0xB2FFFFFF), 18 | fillControlSecondary: Color = Color(0x80F9F9F9), 19 | fillControlTertiary: Color = Color(0x4DF9F9F9), 20 | fillControlDisabled: Color = Color(0x4DF9F9F9), 21 | fillControlTransparent: Color = Color.Transparent, 22 | fillControlInputActive: Color = Color(0xFFFFFFFF), 23 | fillControlAltTransparent: Color = Color.Transparent, 24 | fillControlAltSecondary: Color = Color(0x06000000), 25 | fillControlAltTertiary: Color = Color(0x0F000000), 26 | fillControlAltQuarternary: Color = Color(0x18000000), 27 | fillControlAltDisabled: Color = Color.Transparent, 28 | fillControlStrongDefault: Color = Color(0x72000000), 29 | fillControlStrongDisabled: Color = Color(0x51000000), 30 | fillControlOnImageDefault: Color = Color(0xC9FFFFFF), 31 | fillControlOnImageSecondary: Color = Color(0xFFF3F3F3), 32 | fillControlOnImageTertiary: Color = Color(0xFFEBEBEB), 33 | fillControlOnImageDisabled: Color = Color.Transparent, 34 | fillSubtleTransparent: Color = Color.Transparent, 35 | fillSubtleSecondary: Color = Color(0x0A000000), 36 | fillSubtleTertiary: Color = Color(0x06000000), 37 | fillSubtleDisabled: Color = Color.Transparent, 38 | fillAccentDefault: Color = Color(0xFF005FB8), 39 | fillAccentSecondary: Color = Color(0xE5005FB8), 40 | fillAccentTertiary: Color = Color(0xCC005FB8), 41 | fillAccentDisabled: Color = Color(0x37000000), 42 | fillAccentSelectedTextBackground: Color = Color(0xFF0078D4), 43 | strokeControlDefault: Color = Color(0x0F000000), 44 | strokeControlSecondary: Color = Color(0x29000000), 45 | strokeControlOnAccentDefault: Color = Color(0x14FFFFFF), 46 | strokeControlOnAccentSecondary: Color = Color(0x66000000), 47 | strokeControlOnAccentTertiary: Color = Color(0x37000000), 48 | strokeControlOnAccentDisabled: Color = Color(0x0F000000), 49 | strokeControlForStrongFillWhenOnImage: Color = Color(0x59FFFFFF), 50 | strokeCardDefault: Color = Color(0x0F000000), 51 | strokeCardDefaultSolid: Color = Color(0xFFEBEBEB), 52 | strokeControlStrongDefault: Color = Color(0x72000000), 53 | strokeControlStrongDisabled: Color = Color(0x37000000), 54 | strokeSurfaceDefault: Color = Color(0x66757575), 55 | strokeSurfaceFlyout: Color = Color(0x0F000000), 56 | strokeDivider: Color = Color(0x14000000), 57 | strokeFocusOuter: Color = Color(0xE4000000), 58 | strokeFocusInner: Color = Color(0xFFFFFFFF), 59 | backgroundCardDefault: Color = Color(0xB2FFFFFF), 60 | backgroundCardSecondary: Color = Color(0x80F6F6F6), 61 | backgroundSmoke: Color = Color(0x4D000000), 62 | backgroundLayerDefault: Color = Color(0x80FFFFFF), 63 | backgroundLayerAlt: Color = Color(0xFFFFFFFF), 64 | backgroundLayerOnAcrylic: Color = Color(0x40FFFFFF), 65 | backgroundSolidBase: Color = Color(0xFFF3F3F3), 66 | backgroundSolidSecondary: Color = Color(0xFFEEEEEE), 67 | backgroundSolidTertiary: Color = Color(0xFFF9F9F9), 68 | backgroundSolidQuarternary: Color = Color(0xFFFFFFFF), 69 | backgroundMicaBase: Color = Color(0xFFF3F3F3), 70 | backgroundAcrylicDefault: Color = Color(0xD9FCFCFC), 71 | backgroundAcrylicBase: Color = Color(0xE5F3F3F3), 72 | backgroundAcrylicAccentDefault: Color = Color(0xE599EBFF), 73 | backgroundAcrylicAccentBase: Color = Color(0xE599EBFF), 74 | systemAttention: Color = Color(0xFF005FB7), 75 | systemAttentionBackground: Color = Color(0x80F6F6F6), 76 | systemAttentionSolidBackground: Color = Color(0xFFF7F7F7), 77 | systemSuccess: Color = Color(0xFF0F7B0F), 78 | systemSuccessBackground: Color = Color(0xFFDFF6DD), 79 | systemCaution: Color = Color(0xFF9D5D00), 80 | systemCautionBackground: Color = Color(0xFFDFF6DD), 81 | systemCritical: Color = Color(0xFFC42B1C), 82 | systemCriticalBackground: Color = Color(0xFFFDE7E9), 83 | systemNeutral: Color = Color(0x72000000), 84 | systemNeutralBackground: Color = Color(0x06000000), 85 | systemNeutralSolid: Color = Color(0xFF8A8A8A), 86 | systemNeutralSolidBackground: Color = Color(0xFFF3F3F3), 87 | ): ColorScheme { 88 | return ColorScheme( 89 | textPrimary = textPrimary, 90 | textSecondary = textSecondary, 91 | textTertiary = textTertiary, 92 | textDisabled = textDisabled, 93 | textAccentPrimary = textAccentPrimary, 94 | textAccentSecondary = textAccentSecondary, 95 | textAccentTertiary = textAccentTertiary, 96 | textAccentDisabled = textAccentDisabled, 97 | textOnAccentPrimary = textOnAccentPrimary, 98 | textOnAccentSecondary = textOnAccentSecondary, 99 | textOnAccentDisabled = textOnAccentDisabled, 100 | textOnAccentSelected = textOnAccentSelected, 101 | fillControlDefault = fillControlDefault, 102 | fillControlSecondary = fillControlSecondary, 103 | fillControlTertiary = fillControlTertiary, 104 | fillControlDisabled = fillControlDisabled, 105 | fillControlTransparent = fillControlTransparent, 106 | fillControlInputActive = fillControlInputActive, 107 | fillControlAltTransparent = fillControlAltTransparent, 108 | fillControlAltSecondary = fillControlAltSecondary, 109 | fillControlAltTertiary = fillControlAltTertiary, 110 | fillControlAltQuarternary = fillControlAltQuarternary, 111 | fillControlAltDisabled = fillControlAltDisabled, 112 | fillControlStrongDefault = fillControlStrongDefault, 113 | fillControlStrongDisabled = fillControlStrongDisabled, 114 | fillControlOnImageDefault = fillControlOnImageDefault, 115 | fillControlOnImageSecondary = fillControlOnImageSecondary, 116 | fillControlOnImageTertiary = fillControlOnImageTertiary, 117 | fillControlOnImageDisabled = fillControlOnImageDisabled, 118 | fillSubtleTransparent = fillSubtleTransparent, 119 | fillSubtleSecondary = fillSubtleSecondary, 120 | fillSubtleTertiary = fillSubtleTertiary, 121 | fillSubtleDisabled = fillSubtleDisabled, 122 | fillAccentDefault = fillAccentDefault, 123 | fillAccentSecondary = fillAccentSecondary, 124 | fillAccentTertiary = fillAccentTertiary, 125 | fillAccentDisabled = fillAccentDisabled, 126 | fillAccentSelectedTextBackground = fillAccentSelectedTextBackground, 127 | strokeControlDefault = strokeControlDefault, 128 | strokeControlSecondary = strokeControlSecondary, 129 | strokeControlOnAccentDefault = strokeControlOnAccentDefault, 130 | strokeControlOnAccentSecondary = strokeControlOnAccentSecondary, 131 | strokeControlOnAccentTertiary = strokeControlOnAccentTertiary, 132 | strokeControlOnAccentDisabled = strokeControlOnAccentDisabled, 133 | strokeControlForStrongFillWhenOnImage = strokeControlForStrongFillWhenOnImage, 134 | strokeCardDefault = strokeCardDefault, 135 | strokeCardDefaultSolid = strokeCardDefaultSolid, 136 | strokeControlStrongDefault = strokeControlStrongDefault, 137 | strokeControlStrongDisabled = strokeControlStrongDisabled, 138 | strokeSurfaceDefault = strokeSurfaceDefault, 139 | strokeSurfaceFlyout = strokeSurfaceFlyout, 140 | strokeDivider = strokeDivider, 141 | strokeFocusOuter = strokeFocusOuter, 142 | strokeFocusInner = strokeFocusInner, 143 | backgroundCardDefault = backgroundCardDefault, 144 | backgroundCardSecondary = backgroundCardSecondary, 145 | backgroundSmoke = backgroundSmoke, 146 | backgroundLayerDefault = backgroundLayerDefault, 147 | backgroundLayerAlt = backgroundLayerAlt, 148 | backgroundLayerOnAcrylic = backgroundLayerOnAcrylic, 149 | backgroundSolidBase = backgroundSolidBase, 150 | backgroundSolidSecondary = backgroundSolidSecondary, 151 | backgroundSolidTertiary = backgroundSolidTertiary, 152 | backgroundSolidQuarternary = backgroundSolidQuarternary, 153 | backgroundMicaBase = backgroundMicaBase, 154 | backgroundAcrylicDefault = backgroundAcrylicDefault, 155 | backgroundAcrylicBase = backgroundAcrylicBase, 156 | backgroundAcrylicAccentDefault = backgroundAcrylicAccentDefault, 157 | backgroundAcrylicAccentBase = backgroundAcrylicAccentBase, 158 | systemAttention = systemAttention, 159 | systemAttentionBackground = systemAttentionBackground, 160 | systemAttentionSolidBackground = systemAttentionSolidBackground, 161 | systemSuccess = systemSuccess, 162 | systemSuccessBackground = systemSuccessBackground, 163 | systemCaution = systemCaution, 164 | systemCautionBackground = systemCautionBackground, 165 | systemCritical = systemCritical, 166 | systemCriticalBackground = systemCriticalBackground, 167 | systemNeutral = systemNeutral, 168 | systemNeutralBackground = systemNeutralBackground, 169 | systemNeutralSolid = systemNeutralSolid, 170 | systemNeutralSolidBackground = systemNeutralSolidBackground, 171 | isDark = false 172 | ) 173 | } 174 | 175 | fun darkColorScheme( 176 | textPrimary: Color = Color(0xFFFFFFFF), 177 | textSecondary: Color = Color(0xC8FFFFFF), 178 | textTertiary: Color = Color(0x8BFFFFFF), 179 | textDisabled: Color = Color(0x5DFFFFFF), 180 | textAccentPrimary: Color = Color(0xFF99EBFF), 181 | textAccentSecondary: Color = Color(0xFF99EBFF), 182 | textAccentTertiary: Color = Color(0xFF60CDFF), 183 | textAccentDisabled: Color = Color(0x5DFFFFFF), 184 | textOnAccentPrimary: Color = Color(0xFF000000), 185 | textOnAccentSecondary: Color = Color(0x80000000), 186 | textOnAccentDisabled: Color = Color(0x87FFFFFF), 187 | textOnAccentSelected: Color = Color(0xFFFFFFFF), 188 | fillControlDefault: Color = Color(0x0FFFFFFF), 189 | fillControlSecondary: Color = Color(0x15FFFFFF), 190 | fillControlTertiary: Color = Color(0x08FFFFFF), 191 | fillControlDisabled: Color = Color(0x0BFFFFFF), 192 | fillControlTransparent: Color = Color.Transparent, 193 | fillControlInputActive: Color = Color(0xB21E1E1E), 194 | fillControlAltTransparent: Color = Color.Transparent, 195 | fillControlAltSecondary: Color = Color(0x1A000000), 196 | fillControlAltTertiary: Color = Color(0x0BFFFFFF), 197 | fillControlAltQuarternary: Color = Color(0x12FFFFFF), 198 | fillControlAltDisabled: Color = Color.Transparent, 199 | fillControlStrongDefault: Color = Color(0x8BFFFFFF), 200 | fillControlStrongDisabled: Color = Color(0x3FFFFFFF), 201 | fillControlOnImageDefault: Color = Color(0xB21C1C1C), 202 | fillControlOnImageSecondary: Color = Color(0xFF1A1A1A), 203 | fillControlOnImageTertiary: Color = Color(0xFF131313), 204 | fillControlOnImageDisabled: Color = Color.Transparent, 205 | fillSubtleTransparent: Color = Color.Transparent, 206 | fillSubtleSecondary: Color = Color(0x0FFFFFFF), 207 | fillSubtleTertiary: Color = Color(0x0BFFFFFF), 208 | fillSubtleDisabled: Color = Color.Transparent, 209 | fillAccentDefault: Color = Color(0xFF60CDFF), 210 | fillAccentSecondary: Color = Color(0xE560CDFF), 211 | fillAccentTertiary: Color = Color(0xCC60CDFF), 212 | fillAccentDisabled: Color = Color(0x28FFFFFF), 213 | fillAccentSelectedTextBackground: Color = Color(0xFF0078D4), 214 | strokeControlDefault: Color = Color(0x12FFFFFF), 215 | strokeControlSecondary: Color = Color(0x18FFFFFF), 216 | strokeControlOnAccentDefault: Color = Color(0x14FFFFFF), 217 | strokeControlOnAccentSecondary: Color = Color(0x24000000), 218 | strokeControlOnAccentTertiary: Color = Color(0x37000000), 219 | strokeControlOnAccentDisabled: Color = Color(0x33000000), 220 | strokeControlForStrongFillWhenOnImage: Color = Color(0x6B000000), 221 | strokeCardDefault: Color = Color(0x1A000000), 222 | strokeCardDefaultSolid: Color = Color(0xFF1C1C1C), 223 | strokeControlStrongDefault: Color = Color(0x8BFFFFFF), 224 | strokeControlStrongDisabled: Color = Color(0x28FFFFFF), 225 | strokeSurfaceDefault: Color = Color(0x66757575), 226 | strokeSurfaceFlyout: Color = Color(0x33000000), 227 | strokeDivider: Color = Color(0x15FFFFFF), 228 | strokeFocusOuter: Color = Color(0xFFFFFFFF), 229 | strokeFocusInner: Color = Color(0xB2000000), 230 | backgroundCardDefault: Color = Color(0x0DFFFFFF), 231 | backgroundCardSecondary: Color = Color(0x08FFFFFF), 232 | backgroundSmoke: Color = Color(0x4D000000), 233 | backgroundLayerDefault: Color = Color(0x4D3A3A3A), 234 | backgroundLayerAlt: Color = Color(0x0EFFFFFF), 235 | backgroundLayerOnAcrylic: Color = Color(0x09FFFFFF), 236 | backgroundSolidBase: Color = Color(0xFF202020), 237 | backgroundSolidSecondary: Color = Color(0xFF1C1C1C), 238 | backgroundSolidTertiary: Color = Color(0xFF282828), 239 | backgroundSolidQuarternary: Color = Color(0xFF2C2C2C), 240 | backgroundMicaBase: Color = Color(0xFF202020), 241 | backgroundAcrylicDefault: Color = Color(0xF52C2C2C), 242 | backgroundAcrylicBase: Color = Color(0xF5202020), 243 | backgroundAcrylicAccentDefault: Color = Color(0xCC003F92), 244 | backgroundAcrylicAccentBase: Color = Color(0xCC0078D4), 245 | systemAttention: Color = Color(0xFF60CDFF), 246 | systemAttentionBackground: Color = Color(0x08FFFFFF), 247 | systemAttentionSolidBackground: Color = Color(0xFF2E2E2E), 248 | systemSuccess: Color = Color(0xFF6CCB5F), 249 | systemSuccessBackground: Color = Color(0xFF393D1B), 250 | systemCaution: Color = Color(0xFFFCE100), 251 | systemCautionBackground: Color = Color(0xFF433519), 252 | systemCritical: Color = Color(0xFFFF99A4), 253 | systemCriticalBackground: Color = Color(0xFF442726), 254 | systemNeutral: Color = Color(0x8BFFFFFF), 255 | systemNeutralBackground: Color = Color(0x08FFFFFF), 256 | systemNeutralSolid: Color = Color(0xFF9D9D9D), 257 | systemNeutralSolidBackground: Color = Color(0xFF2E2E2E), 258 | ): ColorScheme { 259 | return ColorScheme( 260 | textPrimary = textPrimary, 261 | textSecondary = textSecondary, 262 | textTertiary = textTertiary, 263 | textDisabled = textDisabled, 264 | textAccentPrimary = textAccentPrimary, 265 | textAccentSecondary = textAccentSecondary, 266 | textAccentTertiary = textAccentTertiary, 267 | textAccentDisabled = textAccentDisabled, 268 | textOnAccentPrimary = textOnAccentPrimary, 269 | textOnAccentSecondary = textOnAccentSecondary, 270 | textOnAccentDisabled = textOnAccentDisabled, 271 | textOnAccentSelected = textOnAccentSelected, 272 | fillControlDefault = fillControlDefault, 273 | fillControlSecondary = fillControlSecondary, 274 | fillControlTertiary = fillControlTertiary, 275 | fillControlDisabled = fillControlDisabled, 276 | fillControlTransparent = fillControlTransparent, 277 | fillControlInputActive = fillControlInputActive, 278 | fillControlAltTransparent = fillControlAltTransparent, 279 | fillControlAltSecondary = fillControlAltSecondary, 280 | fillControlAltTertiary = fillControlAltTertiary, 281 | fillControlAltQuarternary = fillControlAltQuarternary, 282 | fillControlAltDisabled = fillControlAltDisabled, 283 | fillControlStrongDefault = fillControlStrongDefault, 284 | fillControlStrongDisabled = fillControlStrongDisabled, 285 | fillControlOnImageDefault = fillControlOnImageDefault, 286 | fillControlOnImageSecondary = fillControlOnImageSecondary, 287 | fillControlOnImageTertiary = fillControlOnImageTertiary, 288 | fillControlOnImageDisabled = fillControlOnImageDisabled, 289 | fillSubtleTransparent = fillSubtleTransparent, 290 | fillSubtleSecondary = fillSubtleSecondary, 291 | fillSubtleTertiary = fillSubtleTertiary, 292 | fillSubtleDisabled = fillSubtleDisabled, 293 | fillAccentDefault = fillAccentDefault, 294 | fillAccentSecondary = fillAccentSecondary, 295 | fillAccentTertiary = fillAccentTertiary, 296 | fillAccentDisabled = fillAccentDisabled, 297 | fillAccentSelectedTextBackground = fillAccentSelectedTextBackground, 298 | strokeControlDefault = strokeControlDefault, 299 | strokeControlSecondary = strokeControlSecondary, 300 | strokeControlOnAccentDefault = strokeControlOnAccentDefault, 301 | strokeControlOnAccentSecondary = strokeControlOnAccentSecondary, 302 | strokeControlOnAccentTertiary = strokeControlOnAccentTertiary, 303 | strokeControlOnAccentDisabled = strokeControlOnAccentDisabled, 304 | strokeControlForStrongFillWhenOnImage = strokeControlForStrongFillWhenOnImage, 305 | strokeCardDefault = strokeCardDefault, 306 | strokeCardDefaultSolid = strokeCardDefaultSolid, 307 | strokeControlStrongDefault = strokeControlStrongDefault, 308 | strokeControlStrongDisabled = strokeControlStrongDisabled, 309 | strokeSurfaceDefault = strokeSurfaceDefault, 310 | strokeSurfaceFlyout = strokeSurfaceFlyout, 311 | strokeDivider = strokeDivider, 312 | strokeFocusOuter = strokeFocusOuter, 313 | strokeFocusInner = strokeFocusInner, 314 | backgroundCardDefault = backgroundCardDefault, 315 | backgroundCardSecondary = backgroundCardSecondary, 316 | backgroundSmoke = backgroundSmoke, 317 | backgroundLayerDefault = backgroundLayerDefault, 318 | backgroundLayerAlt = backgroundLayerAlt, 319 | backgroundLayerOnAcrylic = backgroundLayerOnAcrylic, 320 | backgroundSolidBase = backgroundSolidBase, 321 | backgroundSolidSecondary = backgroundSolidSecondary, 322 | backgroundSolidTertiary = backgroundSolidTertiary, 323 | backgroundSolidQuarternary = backgroundSolidQuarternary, 324 | backgroundMicaBase = backgroundMicaBase, 325 | backgroundAcrylicDefault = backgroundAcrylicDefault, 326 | backgroundAcrylicBase = backgroundAcrylicBase, 327 | backgroundAcrylicAccentDefault = backgroundAcrylicAccentDefault, 328 | backgroundAcrylicAccentBase = backgroundAcrylicAccentBase, 329 | systemAttention = systemAttention, 330 | systemAttentionBackground = systemAttentionBackground, 331 | systemAttentionSolidBackground = systemAttentionSolidBackground, 332 | systemSuccess = systemSuccess, 333 | systemSuccessBackground = systemSuccessBackground, 334 | systemCaution = systemCaution, 335 | systemCautionBackground = systemCautionBackground, 336 | systemCritical = systemCritical, 337 | systemCriticalBackground = systemCriticalBackground, 338 | systemNeutral = systemNeutral, 339 | systemNeutralBackground = systemNeutralBackground, 340 | systemNeutralSolid = systemNeutralSolid, 341 | systemNeutralSolidBackground = systemNeutralSolidBackground, 342 | isDark = true 343 | ) 344 | } 345 | 346 | @Stable 347 | class ColorScheme( 348 | textPrimary: Color, 349 | textSecondary: Color, 350 | textTertiary: Color, 351 | textDisabled: Color, 352 | textAccentPrimary: Color, 353 | textAccentSecondary: Color, 354 | textAccentTertiary: Color, 355 | textAccentDisabled: Color, 356 | textOnAccentPrimary: Color, 357 | textOnAccentSecondary: Color, 358 | textOnAccentDisabled: Color, 359 | textOnAccentSelected: Color, 360 | fillControlDefault: Color, 361 | fillControlSecondary: Color, 362 | fillControlTertiary: Color, 363 | fillControlDisabled: Color, 364 | fillControlTransparent: Color, 365 | fillControlInputActive: Color, 366 | fillControlAltTransparent: Color, 367 | fillControlAltSecondary: Color, 368 | fillControlAltTertiary: Color, 369 | fillControlAltQuarternary: Color, 370 | fillControlAltDisabled: Color, 371 | fillControlStrongDefault: Color, 372 | fillControlStrongDisabled: Color, 373 | fillControlOnImageDefault: Color, 374 | fillControlOnImageSecondary: Color, 375 | fillControlOnImageTertiary: Color, 376 | fillControlOnImageDisabled: Color, 377 | fillSubtleTransparent: Color, 378 | fillSubtleSecondary: Color, 379 | fillSubtleTertiary: Color, 380 | fillSubtleDisabled: Color, 381 | fillAccentDefault: Color, 382 | fillAccentSecondary: Color, 383 | fillAccentTertiary: Color, 384 | fillAccentDisabled: Color, 385 | fillAccentSelectedTextBackground: Color, 386 | strokeControlDefault: Color, 387 | strokeControlSecondary: Color, 388 | strokeControlOnAccentDefault: Color, 389 | strokeControlOnAccentSecondary: Color, 390 | strokeControlOnAccentTertiary: Color, 391 | strokeControlOnAccentDisabled: Color, 392 | strokeControlForStrongFillWhenOnImage: Color, 393 | strokeCardDefault: Color, 394 | strokeCardDefaultSolid: Color, 395 | strokeControlStrongDefault: Color, 396 | strokeControlStrongDisabled: Color, 397 | strokeSurfaceDefault: Color, 398 | strokeSurfaceFlyout: Color, 399 | strokeDivider: Color, 400 | strokeFocusOuter: Color, 401 | strokeFocusInner: Color, 402 | backgroundCardDefault: Color, 403 | backgroundCardSecondary: Color, 404 | backgroundSmoke: Color, 405 | backgroundLayerDefault: Color, 406 | backgroundLayerAlt: Color, 407 | backgroundLayerOnAcrylic: Color, 408 | backgroundSolidBase: Color, 409 | backgroundSolidSecondary: Color, 410 | backgroundSolidTertiary: Color, 411 | backgroundSolidQuarternary: Color, 412 | backgroundMicaBase: Color, 413 | backgroundAcrylicDefault: Color, 414 | backgroundAcrylicBase: Color, 415 | backgroundAcrylicAccentDefault: Color, 416 | backgroundAcrylicAccentBase: Color, 417 | systemAttention: Color, 418 | systemAttentionBackground: Color, 419 | systemAttentionSolidBackground: Color, 420 | systemSuccess: Color, 421 | systemSuccessBackground: Color, 422 | systemCaution: Color, 423 | systemCautionBackground: Color, 424 | systemCritical: Color, 425 | systemCriticalBackground: Color, 426 | systemNeutral: Color, 427 | systemNeutralBackground: Color, 428 | systemNeutralSolid: Color, 429 | systemNeutralSolidBackground: Color, 430 | isDark: Boolean 431 | ) { 432 | var textPrimary by mutableStateOf(textPrimary) 433 | internal set 434 | var textSecondary by mutableStateOf(textSecondary) 435 | internal set 436 | var textTertiary by mutableStateOf(textTertiary) 437 | internal set 438 | var textDisabled by mutableStateOf(textDisabled) 439 | internal set 440 | var textAccentPrimary by mutableStateOf(textAccentPrimary) 441 | internal set 442 | var textAccentSecondary by mutableStateOf(textAccentSecondary) 443 | internal set 444 | var textAccentTertiary by mutableStateOf(textAccentTertiary) 445 | internal set 446 | var textAccentDisabled by mutableStateOf(textAccentDisabled) 447 | internal set 448 | var textOnAccentPrimary by mutableStateOf(textOnAccentPrimary) 449 | internal set 450 | var textOnAccentSecondary by mutableStateOf(textOnAccentSecondary) 451 | internal set 452 | var textOnAccentDisabled by mutableStateOf(textOnAccentDisabled) 453 | internal set 454 | var textOnAccentSelected by mutableStateOf(textOnAccentSelected) 455 | internal set 456 | var fillControlDefault by mutableStateOf(fillControlDefault) 457 | internal set 458 | var fillControlSecondary by mutableStateOf(fillControlSecondary) 459 | internal set 460 | var fillControlTertiary by mutableStateOf(fillControlTertiary) 461 | internal set 462 | var fillControlDisabled by mutableStateOf(fillControlDisabled) 463 | internal set 464 | var fillControlTransparent by mutableStateOf(fillControlTransparent) 465 | internal set 466 | var fillControlInputActive by mutableStateOf(fillControlInputActive) 467 | internal set 468 | var fillControlAltTransparent by mutableStateOf(fillControlAltTransparent) 469 | internal set 470 | var fillControlAltSecondary by mutableStateOf(fillControlAltSecondary) 471 | internal set 472 | var fillControlAltTertiary by mutableStateOf(fillControlAltTertiary) 473 | internal set 474 | var fillControlAltQuarternary by mutableStateOf(fillControlAltQuarternary) 475 | internal set 476 | var fillControlAltDisabled by mutableStateOf(fillControlAltDisabled) 477 | internal set 478 | var fillControlStrongDefault by mutableStateOf(fillControlStrongDefault) 479 | internal set 480 | var fillControlStrongDisabled by mutableStateOf(fillControlStrongDisabled) 481 | internal set 482 | var fillControlOnImageDefault by mutableStateOf(fillControlOnImageDefault) 483 | internal set 484 | var fillControlOnImageSecondary by mutableStateOf(fillControlOnImageSecondary) 485 | internal set 486 | var fillControlOnImageTertiary by mutableStateOf(fillControlOnImageTertiary) 487 | internal set 488 | var fillControlOnImageDisabled by mutableStateOf(fillControlOnImageDisabled) 489 | internal set 490 | var fillSubtleTransparent by mutableStateOf(fillSubtleTransparent) 491 | internal set 492 | var fillSubtleSecondary by mutableStateOf(fillSubtleSecondary) 493 | internal set 494 | var fillSubtleTertiary by mutableStateOf(fillSubtleTertiary) 495 | internal set 496 | var fillSubtleDisabled by mutableStateOf(fillSubtleDisabled) 497 | internal set 498 | var fillAccentDefault by mutableStateOf(fillAccentDefault) 499 | internal set 500 | var fillAccentSecondary by mutableStateOf(fillAccentSecondary) 501 | internal set 502 | var fillAccentTertiary by mutableStateOf(fillAccentTertiary) 503 | internal set 504 | var fillAccentDisabled by mutableStateOf(fillAccentDisabled) 505 | internal set 506 | var fillAccentSelectedTextBackground by mutableStateOf(fillAccentSelectedTextBackground) 507 | internal set 508 | var strokeControlDefault by mutableStateOf(strokeControlDefault) 509 | internal set 510 | var strokeControlSecondary by mutableStateOf(strokeControlSecondary) 511 | internal set 512 | var strokeControlOnAccentDefault by mutableStateOf(strokeControlOnAccentDefault) 513 | internal set 514 | var strokeControlOnAccentSecondary by mutableStateOf(strokeControlOnAccentSecondary) 515 | internal set 516 | var strokeControlOnAccentTertiary by mutableStateOf(strokeControlOnAccentTertiary) 517 | internal set 518 | var strokeControlOnAccentDisabled by mutableStateOf(strokeControlOnAccentDisabled) 519 | internal set 520 | var strokeControlForStrongFillWhenOnImage by mutableStateOf(strokeControlForStrongFillWhenOnImage) 521 | internal set 522 | var strokeCardDefault by mutableStateOf(strokeCardDefault) 523 | internal set 524 | var strokeCardDefaultSolid by mutableStateOf(strokeCardDefaultSolid) 525 | internal set 526 | var strokeControlStrong by mutableStateOf(strokeControlStrongDefault) 527 | internal set 528 | var strokeControlStrongDisabled by mutableStateOf(strokeControlStrongDisabled) 529 | internal set 530 | var strokeSurfaceDefault by mutableStateOf(strokeSurfaceDefault) 531 | internal set 532 | var strokeSurfaceFlyout by mutableStateOf(strokeSurfaceFlyout) 533 | internal set 534 | var strokeDivider by mutableStateOf(strokeDivider) 535 | internal set 536 | var strokeFocusOuter by mutableStateOf(strokeFocusOuter) 537 | internal set 538 | var strokeFocusInner by mutableStateOf(strokeFocusInner) 539 | internal set 540 | var backgroundCardDefault by mutableStateOf(backgroundCardDefault) 541 | internal set 542 | var backgroundCardSecondary by mutableStateOf(backgroundCardSecondary) 543 | internal set 544 | var backgroundSmoke by mutableStateOf(backgroundSmoke) 545 | internal set 546 | var backgroundLayerDefault by mutableStateOf(backgroundLayerDefault) 547 | internal set 548 | var backgroundLayerAlt by mutableStateOf(backgroundLayerAlt) 549 | internal set 550 | var backgroundLayerOnAcrylic by mutableStateOf(backgroundLayerOnAcrylic) 551 | internal set 552 | var backgroundSolidBase by mutableStateOf(backgroundSolidBase) 553 | internal set 554 | var backgroundSolidSecondary by mutableStateOf(backgroundSolidSecondary) 555 | internal set 556 | var backgroundSolidTertiary by mutableStateOf(backgroundSolidTertiary) 557 | internal set 558 | var backgroundSolidQuarternary by mutableStateOf(backgroundSolidQuarternary) 559 | internal set 560 | var backgroundMicaBase by mutableStateOf(backgroundMicaBase) 561 | internal set 562 | var backgroundAcrylicDefault by mutableStateOf(backgroundAcrylicDefault) 563 | internal set 564 | var backgroundAcrylicBase by mutableStateOf(backgroundAcrylicBase) 565 | internal set 566 | var backgroundAcrylicAccentDefault by mutableStateOf(backgroundAcrylicAccentDefault) 567 | internal set 568 | var backgroundAcrylicAccentBase by mutableStateOf(backgroundAcrylicAccentBase) 569 | internal set 570 | var systemAttention by mutableStateOf(systemAttention) 571 | internal set 572 | var systemAttentionBackground by mutableStateOf(systemAttentionBackground) 573 | internal set 574 | var systemAttentionSolidBackground by mutableStateOf(systemAttentionSolidBackground) 575 | internal set 576 | var systemSuccess by mutableStateOf(systemSuccess) 577 | internal set 578 | var systemSuccessBackground by mutableStateOf(systemSuccessBackground) 579 | internal set 580 | var systemCaution by mutableStateOf(systemCaution) 581 | internal set 582 | var systemCautionBackground by mutableStateOf(systemCautionBackground) 583 | internal set 584 | var systemCritical by mutableStateOf(systemCritical) 585 | internal set 586 | var systemCriticalBackground by mutableStateOf(systemCriticalBackground) 587 | internal set 588 | var systemNeutral by mutableStateOf(systemNeutral) 589 | internal set 590 | var systemNeutralBackground by mutableStateOf(systemNeutralBackground) 591 | internal set 592 | var systemNeutralSolid by mutableStateOf(systemNeutralSolid) 593 | internal set 594 | var systemNeutralSolidBackground by mutableStateOf(systemNeutralSolidBackground) 595 | internal set 596 | var isDark by mutableStateOf(isDark) 597 | internal set 598 | } 599 | 600 | internal val LocalColorScheme = staticCompositionLocalOf { lightColorScheme() } --------------------------------------------------------------------------------