Compose Fluent UI

repository·master·Indexed 20 days ago

https://github.com/compose-fluent/compose-fluent-ui

An experimental UI library for Compose Multiplatform that implements the Microsoft Fluent Design System. It provides components, themes, and layers such as Mica and Acrylic to create cross-platform applications with a Windows-inspired aesthetic. Supports desktop (Linux, macOS, Windows), iOS, Android, and Web (WasmJs, JS). Includes a fluent-icons-generator module for converting Fluent UI SVG icons into Kotlin code.

Tokens
7.7K
Snippets
28
Records
38
Agent score
72%

What's inside Compose Fluent UI

  1. Understand the Material Iconography module structure

    master

    Material iconography is distributed across several modules to balance size and usability:

    • generator: A build-time module that processes and generates Kotlin source files from raw icons. It is not shipped as an artifact and caches outputs based on inputs in generator/raw-icons.
    • material-icons-core: Contains the most commonly used icons, including those required by Material components (e.g., the menu icon). This is a small module and is a dependency for the material module.
    • material-icons-extended: Contains all icons not present in core (over 5000 icons).
      • Warning: Due to its excessive size, do NOT include this as a direct dependency of other libraries. It should only be used if Proguard or R8 is enabled.
    • material-icons-extended-$theme: Specific theme modules for material-icons-extended designed to facilitate faster parallel compilation.
  2. How icon generation works

    master

    Icon generation is a multi-step build process:

    1. Download: Vector drawables are downloaded via the Google Fonts API using a script in the generator module and stored in raw-icons.
    2. Processing & API Tracking: During compilation of core and extended modules, icons are processed to remove incompatible theme attributes and verified for theme consistency. An API tracking file is generated; if the generated API differs from the checked-in version, the build fails to ensure API stability.
    3. Parsing & Code Generation: Processed XML files are parsed into a VectorAssetBuilder representation. This is converted into Kotlin source files where each icon is represented as a by lazy property. These files are written to the output directory and compiled as part of the module's source code.

    To use the generated icons in your code, refer to androidx.compose.material.icons.Icons.

  3. Add a component example page to the Gallery

    master

    The Gallery app uses KSP and KotlinPoet to automatically generate navigation and example code. To add a new component page, follow these steps:

    1. Create a Screen file: Create [YourComponent]Screen.kt in the package corresponding to the component group.
    2. Annotate the Screen method: Create a @Composable function named [YourComponent]Screen and annotate it with @Component(index = ..., description = "...").
    3. Implement GalleryPage: Use the GalleryPage method within your screen. It requires componentPath (from FluentSourceFile) and galleryPath (from ComponentPagePath).
    4. Define Sections: Inside GalleryPage, use the Section method for each example.
      • title: String title of the section.
      • content: The @Composable code for the example.
      • output: (Optional) A @Composable block to display state changes.
      • options: (Optional) A @Composable block for user controls (e.g., CheckBoxes).
      • sourceCode: The global variable generated by the @Sample annotation.
    5. Create the @Sample method: Create a private @Composable method named [YourComponentCase]Sample and annotate it with @Sample. This generates the sourceCodeOf[YourComponentCase]Sample variable used in the Section.

    Example implementation:

    // ButtonScreen.kt
    package io.github.composefluent.gallery.screen.basicinput
    
    @Component(index = 0, description = "A control that responds to user input")
    @Composable
    fun ButtonScreen() {
        GalleryPage(
            componentPath = FluentSourceFile.Button,
            galleryPath = ComponentPagePath.ButtonScreen,
        ) {
            val clickTextContent = remember { mutableStateOf("") }
            val buttonEnabled = remember { mutableStateOf(true) }
            Section(
                title = "A simple Button with text content.",
                content = { 
                    ButtonSample(enabled = buttonEnabled.value) { 
                        clickTextContent.value = "You clicked: Button 1" 
                    } 
                },
                output = { 
                    if (clickTextContent.value.isNotBlank()) {
                        Text(clickTextContent.value)
                    }
                },
                options = { 
                    CheckBox(
                        checked = !buttonEnabled.value,
                        onCheckStateChange = { buttonEnabled.value = !it },
                        label = "Disable button"
                    ) 
                },
                sourceCode = sourceCodeOfButtonSample
            )
        }
    }
    
    @Sample
    @Composable
    private fun ButtonSample(enabled: Boolean = true, onClick: () -> Unit) {
        Button(disabled = !enabled, onClick = onClick) {
            Text("Standard Compose Button")
        }
    }
    // ButtonScreen.kt
    package io.github.composefluent.gallery.screen.basicinput
    
    @Component(index = 0, description = "A control that responds to user input")
    @Composable
    fun ButtonScreen() {
        GalleryPage(
            componentPath = FluentSourceFile.Button,
            galleryPath = ComponentPagePath.ButtonScreen,
        ) {
            val clickTextContent = remember { mutableStateOf("") }
            val buttonEnabled = remember { mutableStateOf(true) }
            Section(
                title = "A simple Button with text content.",
                content = { 
                    ButtonSample(enabled = buttonEnabled.value) { 
                        clickTextContent.value = "You clicked: Button 1" 
                    } 
                },
                output = { 
                    if (clickTextContent.value.isNotBlank()) {
                        Text(clickTextContent.value)
                    }
                },
                options = { 
                    CheckBox(
                        checked = !buttonEnabled.value,
                        onCheckStateChange = { buttonEnabled.value = !it },
                        label = "Disable button"
                    ) 
                },
                sourceCode = sourceCodeOfButtonSample
            )
        }
    }
    
    @Sample
    @Composable
    private fun ButtonSample(enabled: Boolean = true, onClick: () -> Unit) {
        Button(disabled = !enabled, onClick = onClick) {
            Text("Standard Compose Button")
        }
    }
  4. Develop new components for Compose-Fluent-UI

    master

    When creating new components, ensure they strictly adhere to the Windows UI Kit Figma guidelines for layout, typography, and color.

    1. Source Code Location: Create component source files in the fluent/components directory.
    2. Internal Classes: If a component requires internal helper classes (e.g., layout utilities like OverflowRow), place them in the appropriate package (e.g., /layout).
    3. Animations: If animation parameters are uncertain, use a TODO note within the code.

    Example component structure:

    // Button.kt
    package io.github.composefluent.components
    // Button.kt
    package io.github.composefluent.components
  5. Install Compose Fluent UI

    master

    To use Compose Fluent UI in your Compose Multiplatform project, add the following dependencies to your build.gradle.kts file. If you require the full set of Fluent icons, also include the fluent-icons-extended artifact.

    implementation("io.github.compose-fluent:fluent:v0.1.0")
    implementation("io.github.compose-fluent:fluent-icons-extended:v0.1.0") // If you want to use full fluent icons.
  6. Generate Fluent Icons from SVG

    master

    The fluent-icons-generator module provides a workflow to convert Fluent UI icons from the web catalog into usable Kotlin code for Compose. The process involves three main stages: extracting SVGs from a saved HTML catalog, converting those SVGs to Android XML Vector Drawables, and finally converting the XML into Kotlin code.

    Workflow Steps

    1. Extract SVGs from Catalog

      • Access the Fluent UI Catalog.
      • Save the page as an HTML file named icons-catalog.html.
      • Run the ExtractSvgFromCatalogKt task/script.
    2. Convert to XML

      • Run the ConvertToXmlKt task/script to transform the extracted SVGs into XML Vector Drawables (using logic derived from Android Studio's vector tools).
    3. Convert to Kotlin Code

      • Run the ConvertToCodeKt task/script to generate the final Kotlin icon implementations (using logic derived from Jetpack Compose icon generators).
    # Workflow Summary
    1. Save page as html to `icons-catalog.html`
    2. Run `ExtractSvgFromCatalogKt`
    3. Run `ConvertToXmlKt`
    4. Run `ConvertToCodeKt`
  7. Quick Start with FluentTheme and Mica

    master

    To start using the library, wrap your application in FluentTheme. This serves as the entry point and provides the design context, similar to MaterialTheme. You can use background effects like Mica to achieve the Fluent look.

    Note:

    • Components are located in the io.github.composefluent.component package.
    • Mica and Layer are located in the background package.
    import io.github.composefluent.component.*
    
    @Composable
    fun App() {
      FluentTheme {
        Mica(Modifier.fillMaxSize()) {
          Column(Modifier.padding(24.dp)) {
            Button(onClick = {}) {
              Text("Hello Fluent Design")
            }
          }
        }
      }
    }
  8. Use Snapshot versions of Compose Fluent UI

    master

    Snapshot versions are automatically published from the dev branch. To use them, you must first add the Sonatype snapshot repository to your repositories block in build.gradle.kts, then use the -SNAPSHOT suffix in your dependency declaration.

    repositories {
        mavenCentral()
        // Add Sonatype snapshots repository
        maven("https://central.sonatype.com/repository/maven-snapshots/")
    }
    
    // Then specify the snapshot dependency
    implementation("io.github.compose-fluent:fluent:0.1.0-SNAPSHOT")
  9. Add new icons to the library

    master

    To add new icons to the Material Iconography set, follow these steps:

    1. Run the icon downloading script located at generator/download_material_icons.py.
    2. Execute a Gradle command that triggers compilation of the icon modules (for example: ./gradlew buildOnServer).
    3. If the build fails due to API changes, follow the error message instructions to confirm the changes by updating the API tracking file.
    ./gradlew buildOnServer
  10. Configure component groups in `ComponentGroupInfo`

    master

    If a component does not fit into an existing group, you must add a new group to the ComponentGroupInfo object. This defines the navigation structure in the Gallery.

    Use the @ComponentGroup annotation with the following parameters:

    • name: The icon/identifier for the group.
    • index: The display order.
    • packageMap: The package string where the component screens are located.

    Example:

    // ComponentGroupInfo.kt
    package io.github.composefluent.gallery.component
    
    object ComponentGroupInfo {
        private const val screenPackage: String = "io.github.composefluent.gallery.screen"
    
        @ComponentGroup("CheckboxChecked", index = 2, packageMap = "$screenPackage.basicinput")
        const val BasicInput = "Basic input"
    }
    // ComponentGroupInfo.kt
    package io.github.composefluent.gallery.component
    
    object ComponentGroupInfo {
        private const val screenPackage: String = "io.github.composefluent.gallery.screen"
    
        @ComponentGroup("CheckboxChecked", index = 2, packageMap = "$screenPackage.basicinput")
        const val BasicInput = "Basic input"
    }