Showing preview only (465K chars total). Download the full file or copy to clipboard to get everything.
Repository: ButterCam/compose-jetbrains-theme
Branch: main
Commit: 8f237fd0c144
Files: 104
Total size: 428.5 KB
Directory structure:
gitextract_99aa8iq5/
├── .github/
│ └── workflows/
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle.kts
├── classic/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ └── io/
│ └── kanro/
│ └── compose/
│ └── jetbrains/
│ ├── JBTheme.kt
│ ├── JBTypography.kt
│ ├── color/
│ │ ├── ButtonColors.kt
│ │ ├── CheckBoxColors.kt
│ │ ├── FieldColors.kt
│ │ ├── FocusColors.kt
│ │ ├── IconColors.kt
│ │ ├── PanelColors.kt
│ │ ├── ProgressColors.kt
│ │ ├── ScrollColors.kt
│ │ ├── SelectionColors.kt
│ │ ├── TabColors.kt
│ │ ├── TableColors.kt
│ │ ├── TextColors.kt
│ │ ├── ToggleColors.kt
│ │ └── ToolBarColors.kt
│ ├── control/
│ │ ├── ActionButton.kt
│ │ ├── Button.kt
│ │ ├── CheckBox.kt
│ │ ├── ComboBox.kt
│ │ ├── ContentAlpha.kt
│ │ ├── ContentColor.kt
│ │ ├── ContextMenu.kt
│ │ ├── DropdownMenu.kt
│ │ ├── EmbeddedTextField.kt
│ │ ├── Icon.kt
│ │ ├── JBToolBar.kt
│ │ ├── JBTree.kt
│ │ ├── JPanel.kt
│ │ ├── ListView.kt
│ │ ├── ProgressBar.kt
│ │ ├── Selection.kt
│ │ ├── Tab.kt
│ │ ├── Text.kt
│ │ └── TextField.kt
│ └── icons/
│ ├── __JBIcons.kt
│ └── jbicons/
│ ├── __Actions.kt
│ ├── __General.kt
│ ├── actions/
│ │ ├── ArrowExpand.kt
│ │ ├── ArrowExpandDark.kt
│ │ ├── Checkmark.kt
│ │ ├── CheckmarkIndeterminate.kt
│ │ └── Close.kt
│ └── general/
│ ├── ButtonDropTriangle.kt
│ └── ButtonDropTriangleDark.kt
├── expui/
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ └── io/
│ └── kanro/
│ └── compose/
│ └── jetbrains/
│ └── expui/
│ ├── DesktopPlatform.kt
│ ├── control/
│ │ ├── ActionButton.kt
│ │ ├── Button.kt
│ │ ├── CheckBox.kt
│ │ ├── ComboBox.kt
│ │ ├── ContextCompositionLocals.kt
│ │ ├── ContextMenu.kt
│ │ ├── DropdownMenu.kt
│ │ ├── Icon.kt
│ │ ├── Indication.kt
│ │ ├── Label.kt
│ │ ├── Link.kt
│ │ ├── Painter.kt
│ │ ├── PointerInput.kt
│ │ ├── ProgressBar.kt
│ │ ├── RadioButton.kt
│ │ ├── SegmentedButton.kt
│ │ ├── Tab.kt
│ │ ├── TextArea.kt
│ │ ├── TextField.kt
│ │ ├── ToolBar.kt
│ │ └── Tooltip.kt
│ ├── style/
│ │ ├── AreaColors.kt
│ │ ├── AreaProvider.kt
│ │ ├── Border.kt
│ │ └── TextStyle.kt
│ ├── theme/
│ │ ├── DarkTheme.kt
│ │ ├── Fonts.kt
│ │ ├── LightTheme.kt
│ │ └── Theme.kt
│ ├── util/
│ │ ├── CustomWindowDecorationAccessing.kt
│ │ └── UnsafeAccessing.kt
│ └── window/
│ ├── JBWindow.Linux.kt
│ ├── JBWindow.MacOS.kt
│ ├── JBWindow.Windows.kt
│ ├── JBWindow.kt
│ ├── MainToolBar.Basic.kt
│ ├── MainToolBar.Linux.kt
│ ├── MainToolBar.MacOS.kt
│ ├── MainToolBar.Windows.kt
│ └── MainToolBar.kt
├── expui-gallery/
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ └── Main.kt
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
# Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests)
push:
branches: [ main ]
# Trigger the workflow on any pull request
pull_request:
jobs:
# Run Gradle Wrapper Validation Action to verify the wrapper's checksum
gradleValidation:
name: Gradle Wrapper
runs-on: ubuntu-latest
steps:
# Check out current repository
- name: Fetch Sources
uses: actions/checkout@v2.3.4
# Validate wrapper
- name: Gradle Wrapper Validation
uses: gradle/wrapper-validation-action@v1.0.4
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.properties.outputs.version }}
changelog: ${{ steps.properties.outputs.changelog }}
steps:
# Check out current repository
- name: Fetch Sources
uses: actions/checkout@v2
- name: Download JBR
uses: carlosperate/download-file-action@v2
with:
file-url: 'https://cache-redirector.jetbrains.com/intellij-jbr/jbr-17.0.5-linux-x64-b653.14.tar.gz'
file-name: 'jbr-17.0.5.tar.gz'
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: jdkfile
jdkFile: jbr-17.0.5.tar.gz
cache: gradle
# Set environment variables
- name: Export Properties
id: properties
shell: bash
run: |
PROPERTIES="$(./gradlew properties -q)"
VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')"
NAME="$(echo "$PROPERTIES" | grep "^name:" | cut -f2- -d ' ')"
CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)"
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "version=$VERSION"
echo "name=$NAME"
echo "changelog=$CHANGELOG"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "changelog=$CHANGELOG" >> $GITHUB_OUTPUT
# Build artifact using buildPlugin Gradle task
- name: Build
run: ./gradlew build
# Store built plugin as an artifact for downloading
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: "${{ steps.properties.outputs.name }} - ${{ steps.properties.outputs.version }}"
path: ./**/build/libs/*
# Prepare a draft release for GitHub Releases page for the manual verification
# If accepted and published, release workflow would be triggered
releaseDraft:
name: Release Draft
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
steps:
# Check out current repository
- name: Fetch Sources
uses: actions/checkout@v2.3.4
# Remove old release drafts by using the curl request for the available releases with draft flag
- name: Remove Old Release Drafts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/{owner}/{repo}/releases \
--jq '.[] | select(.draft == true) | .id' \
| xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{}
# Create new release draft - which is not publicly visible and requires manual acceptance
- name: Create Release Draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ needs.build.outputs.version }} \
--draft \
--title "v${{ needs.build.outputs.version }}" \
--notes "${{ needs.build.outputs.changelog }}"
================================================
FILE: .github/workflows/release.yml
================================================
# GitHub Actions Workflow created for handling the release process based on the draft release prepared
# with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided.
name: Release
on:
release:
types: [ prereleased, released ]
jobs:
# Prepare and publish the plugin to the Marketplace repository
release:
name: Publish Plugin
runs-on: ubuntu-latest
steps:
# Check out current repository
- name: Fetch Sources
uses: actions/checkout@v2.3.4
with:
ref: ${{ github.event.release.tag_name }}
- name: Download JBR
uses: carlosperate/download-file-action@v2
with:
file-url: 'https://cache-redirector.jetbrains.com/intellij-jbr/jbr-17.0.5-linux-x64-b653.14.tar.gz'
file-name: 'jbr-17.0.5.tar.gz'
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: jdkfile
jdkFile: jbr-17.0.5.tar.gz
cache: gradle
# Update Unreleased section with the current release note
- name: Patch Changelog
run: |
./gradlew patchChangelog --release-note="`cat << EOM
${{ github.event.release.body }}
EOM`"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v3
with:
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
- uses: ButterCam/setup-sisyphus-build@v1
with:
dependency-repositories: local,central,portal,google,snapshot
snapshot-url: https://s01.oss.sonatype.org/content/repositories/snapshots
snapshot-username: ${{ secrets.OSSRH_USERNAME }}
snapshot-password: ${{ secrets.OSSRH_PASSWORD }}
release-url: https://s01.oss.sonatype.org/service/local/staging/deploy/maven2
release-username: ${{ secrets.OSSRH_USERNAME }}
release-password: ${{ secrets.OSSRH_PASSWORD }}
gradle-portal-key: ${{ secrets.GRADLE_PUBLISH_KEY }}
gradle-portal-secret: ${{ secrets.GRADLE_PUBLISH_SECRET }}
gpg-key-name: ${{ secrets.GPG_KEY_NAME }}
# Publish the plugin to the Marketplace
- name: Publish
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
run: ./gradlew publish
# Upload artifact as a release asset
- name: Upload Release Asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ github.event.release.tag_name }} ./**/build/libs/*
# Create pull request
- name: Create Pull Request
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ github.event.release.tag_name }}"
BRANCH="changelog-update-$VERSION"
git config user.email "action@github.com"
git config user.name "GitHub Action"
git checkout -b $BRANCH
git commit -am "Changelog update - $VERSION"
git push --set-upstream origin $BRANCH
gh pr create \
--title ":bookmark: Changelog update - \`$VERSION\`" \
--body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \
--base main \
--head $BRANCH
================================================
FILE: .gitignore
================================================
.gradle
/build/
!foundation/gradle/wrapper/gradle-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
logs/
out/
data/elasticsearch
.DS_Store
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## [Unreleased]
## [2.2.0]
- Upgrade to compose 1.5.2
## [2.1.0]
- Provide BasicMainToolBar #18
- Disable minimize and maximize button when resizeable is false #19
- Fix the custom title bar when MainToolBar Content is empty #20
- Optimize the implementation of themes. Switching themes will not cause full recompose.
- Add text styles for theme
## [2.0.0]
- Exp UI theme support
## [1.1.0]
- Target to jvm 1.11 #11
- Replacing SVG Resources with Compose generated Image Vectors #13
- IntelliJ Dracula colors (Dark Theme) #12
Thanks @DevSrSouza
## [1.0.1]
- Upgrade to compose 1.0.1
## [1.0.0]
- JetBrains UI Kit for Compose desktop
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 ButterCam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# We are planning to migrate to [JetBrains/jewel](https://github.com/JetBrains/jewel)!
We are discussing with JetBrains officials about migrating this project to [JetBrains/jewel](https://github.com/JetBrains/jewel). This will be the focus of our next work.
Track progress in this issue: JetBrains/jewel#28
# JetBrains UI Kit for Compose Desktop
New JetBrains style controls and UI kits for [Compose Desktop](https://www.jetbrains.com/lp/compose/).
Classic JetBrains UI kit have been moved to [here](classic).

## Quick Start
## Requirements
### JetBrains Runtime
To get the best presentation across platforms, this library requires the application to run
on [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime) (JBR).
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime) is a modified version of the JetBrains JDK
distribution, which is widely used across the JetBrains' IDEs such as IntelliJ, GoLand and others.
In this library we use the additional API provided by JetBrains Runtime to customise the title bar in Windows and macOS.
If the `JBWindow` component is not used, it can also be used without the JetBrains Runtime.
When using `JBWindow` in a non-JetBrains Runtime environment, a native title bar may additionally be displayed.
JBR-17 (corresponding to JDK 17) can be downloaded from [the SDK page](https://www.jetbrains.com/help/idea/sdk.html) of
IntelliJ.
Make sure that the [project SDK](https://www.jetbrains.com/help/idea/project-settings-and-structure.html#project-sdk)
and [Gradle JVM](https://www.jetbrains.com/help/idea/gradle-jvm-selection.html) are both JBR-17.
#### Project SDK settings

#### Gradle JVM settings

### `jdk.unsupported` module
Most of the JetBrains Runtime APIs are private to JetBrains, and the class `sun.misc.Unsafe` is used to get access to
these APIs.
You need to add the `jdk.unsupported` module to the compose dependency as in the following code to make it work.
You can also skip this step if you don't need to use the `JBWindow` component.
```kotlin
compose.desktop {
application {
nativeDistributions {
modules("jdk.unsupported")
}
}
}
```
## 1. Add dependency
```kotlin
dependencies {
implementation(compose.desktop.currentOs) {
exclude("org.jetbrains.compose.material")
}
implementation("com.bybutter.compose:compose-jetbrains-expui-theme:2.0.0")
}
```
## 2. Use JBWindow DSL
The optimal display of JBWindow requires JetBrains Runtime, refer to the previous [requirements](#Requirements).
```kotlin
fun main() = application {
JBWindow(
title = "JetBrains ExpUI Gallery",
showTitle = true, // If you want to render your own component in the center of the title bar like Intellij do, disable this to hide the title of the MainToolBar (TitleBar).
theme = LightTheme, // Change the theme here, LightTheme and DarkTheme are provided.
state = rememberWindowState(size = DpSize(900.dp, 700.dp)),
onCloseRequest = {
exitApplication()
},
mainToolBar = {
// Render your own component in the MainToolBar (TitleBar).
Row(
Modifier.mainToolBarItem(
Alignment.End,
true
)
) { // Use the mainToolBarItem modifier to change alignment of components and enable/disable window drag area on this component.
}
}) {
// Window content
}
}
```
## 3. Use LightTheme and DarkTheme DSL
Since `JBWindow` requires JetBrains Runtime, if you don't want to use the `JBWindow` component, you can also use a
normal `Window` with `LightTheme`/`DarkTheme` DSL.
```kotlin
fun main() = application {
Window({}) {
LightTheme {
// Your components here
}
}
}
```
# Screenshot
## MacOS
<img width="1012" alt="截屏2022-11-21 17 11 56" src="https://user-images.githubusercontent.com/9367842/203010912-68f54752-a598-49ad-9e50-9217dba544be.png">
<img width="1012" alt="截屏2022-11-21 17 12 18" src="https://user-images.githubusercontent.com/9367842/203011027-3a835525-f44a-42e0-83f5-b1e27011b9e4.png">
## Windows


## Linux(Ubuntu with Gnome)


================================================
FILE: build.gradle.kts
================================================
plugins {
id("com.bybutter.sisyphus.project") version "2.1.0" apply false
id("com.netflix.nebula.contacts") version "7.0.1"
id("com.netflix.nebula.info") version "12.1.6" apply false
id("com.netflix.nebula.maven-publish") version "20.3.0" apply false
id("com.netflix.nebula.source-jar") version "20.3.0" apply false
id("org.jetbrains.changelog") version "1.3.0"
id("org.jetbrains.compose") version "1.5.2" apply false
kotlin("jvm") version "1.9.10" apply false
}
allprojects {
apply(plugin = "com.netflix.nebula.contacts")
apply(plugin = "com.netflix.nebula.info")
group = "com.bybutter.compose"
version = "2.2.0"
contacts {
addPerson(
"higan@live.cn",
delegateClosureOf<nebula.plugin.contacts.Contact> {
moniker = "higan"
github = "devkanro"
roles.add("owner")
},
)
}
}
subprojects {
apply(plugin = "org.jetbrains.compose")
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() {
kotlinOptions.jvmTarget = "17"
}
repositories {
mavenLocal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
changelog {
version.set(project.version.toString())
groups.set(emptyList())
}
================================================
FILE: classic/README.md
================================================
# JetBrains UI Kit for Compose Desktop
JetBrains style controls and UI for [Compose Desktop](https://www.jetbrains.com/lp/compose/).

## Quick Start
## 1. Add dependency
```kotlin
dependencies {
implementation(compose.desktop.currentOs) {
exclude("org.jetbrains.compose.material")
}
implementation("com.bybutter.compose:compose-jetbrains-theme:2.0.0")
}
```
## 2. JBTheme DSL
```kotlin
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Compose for Desktop",
state = rememberWindowState(width = 300.dp, height = 300.dp)
) {
val count = remember { mutableStateOf(0) }
JBTheme {
JPanel(Modifier.fillMaxSize().jBorder(top = 1.dp, color = JBTheme.panelColors.border)) {
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Button(modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = {
count.value++
}) {
Text(if (count.value == 0) "Hello World" else "Clicked ${count.value}!")
}
Button(modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = {
count.value = 0
}) {
Text("Reset")
}
}
}
}
}
}
```
================================================
FILE: classic/build.gradle.kts
================================================
plugins {
kotlin("jvm")
`java-library`
id("com.netflix.nebula.maven-publish")
id("com.netflix.nebula.source-jar")
id("com.bybutter.sisyphus.project")
id("org.jetbrains.compose")
}
description = "JetBrains UI Kit for Compose Desktop"
dependencies {
implementation(kotlin("stdlib"))
implementation(compose.desktop.common) {
exclude("org.jetbrains.compose.material")
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() {
kotlinOptions.jvmTarget = "17"
kotlinOptions.freeCompilerArgs += listOf(
// "-P", "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/JBTheme.kt
================================================
package io.kanro.compose.jetbrains
import androidx.compose.foundation.LocalScrollbarStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import io.kanro.compose.jetbrains.color.ButtonColors
import io.kanro.compose.jetbrains.color.CheckBoxColors
import io.kanro.compose.jetbrains.color.FieldColors
import io.kanro.compose.jetbrains.color.FocusColors
import io.kanro.compose.jetbrains.color.IconColors
import io.kanro.compose.jetbrains.color.LocalButtonColors
import io.kanro.compose.jetbrains.color.LocalCheckBoxColors
import io.kanro.compose.jetbrains.color.LocalFieldColors
import io.kanro.compose.jetbrains.color.LocalFocusColors
import io.kanro.compose.jetbrains.color.LocalIconColors
import io.kanro.compose.jetbrains.color.LocalPanelColors
import io.kanro.compose.jetbrains.color.LocalProgressColors
import io.kanro.compose.jetbrains.color.LocalScrollColors
import io.kanro.compose.jetbrains.color.LocalSelectionColors
import io.kanro.compose.jetbrains.color.LocalTabColors
import io.kanro.compose.jetbrains.color.LocalTextColors
import io.kanro.compose.jetbrains.color.LocalToolBarColors
import io.kanro.compose.jetbrains.color.PanelColors
import io.kanro.compose.jetbrains.color.ProgressColors
import io.kanro.compose.jetbrains.color.ScrollColors
import io.kanro.compose.jetbrains.color.SelectionColors
import io.kanro.compose.jetbrains.color.TabColors
import io.kanro.compose.jetbrains.color.TextColors
import io.kanro.compose.jetbrains.color.ToolBarColors
import io.kanro.compose.jetbrains.color.darkButtonColors
import io.kanro.compose.jetbrains.color.darkCheckBoxColors
import io.kanro.compose.jetbrains.color.darkFieldColors
import io.kanro.compose.jetbrains.color.darkFocusColors
import io.kanro.compose.jetbrains.color.darkIconColors
import io.kanro.compose.jetbrains.color.darkPanelColors
import io.kanro.compose.jetbrains.color.darkProgressColors
import io.kanro.compose.jetbrains.color.darkScrollColors
import io.kanro.compose.jetbrains.color.darkSelectionColors
import io.kanro.compose.jetbrains.color.darkTabColors
import io.kanro.compose.jetbrains.color.darkTextColors
import io.kanro.compose.jetbrains.color.darkToolBarColors
import io.kanro.compose.jetbrains.color.lightButtonColors
import io.kanro.compose.jetbrains.color.lightCheckBoxColors
import io.kanro.compose.jetbrains.color.lightFieldColors
import io.kanro.compose.jetbrains.color.lightFocusColors
import io.kanro.compose.jetbrains.color.lightIconColors
import io.kanro.compose.jetbrains.color.lightPanelColors
import io.kanro.compose.jetbrains.color.lightProgressColors
import io.kanro.compose.jetbrains.color.lightScrollColors
import io.kanro.compose.jetbrains.color.lightSelectionColors
import io.kanro.compose.jetbrains.color.lightTabColors
import io.kanro.compose.jetbrains.color.lightTextColors
import io.kanro.compose.jetbrains.color.lightToolBarColors
import io.kanro.compose.jetbrains.control.LocalContentColor
import io.kanro.compose.jetbrains.control.ProvideTextStyle
import io.kanro.compose.jetbrains.control.darkSelectionScope
import io.kanro.compose.jetbrains.control.lightSelectionScope
@Composable
fun JBTheme(style: JBThemeStyle, content: @Composable () -> Unit) {
JBTheme(
style = style,
buttonColors = if (style == JBThemeStyle.LIGHT) lightButtonColors() else darkButtonColors(),
fieldColors = if (style == JBThemeStyle.LIGHT) lightFieldColors() else darkFieldColors(),
focusColors = if (style == JBThemeStyle.LIGHT) lightFocusColors() else darkFocusColors(),
panelColors = if (style == JBThemeStyle.LIGHT) lightPanelColors() else darkPanelColors(),
textColors = if (style == JBThemeStyle.LIGHT) lightTextColors() else darkTextColors(),
toolBarColors = if (style == JBThemeStyle.LIGHT) lightToolBarColors() else darkToolBarColors(),
progressColors = if (style == JBThemeStyle.LIGHT) lightProgressColors() else darkProgressColors(),
scrollColors = if (style == JBThemeStyle.LIGHT) lightScrollColors() else darkScrollColors(),
tabColors = if (style == JBThemeStyle.LIGHT) lightTabColors() else darkTabColors(),
selectionColors = if (style == JBThemeStyle.LIGHT) lightSelectionColors() else darkSelectionColors(),
checkBoxColors = if (style == JBThemeStyle.LIGHT) lightCheckBoxColors() else darkCheckBoxColors(),
iconColors = if (style == JBThemeStyle.LIGHT) lightIconColors() else darkIconColors(),
typography = JBTypography(),
iconTheme = if (style == JBThemeStyle.LIGHT) JBThemeStyle.LIGHT else JBThemeStyle.DARK,
selectionScope = if (style == JBThemeStyle.LIGHT) lightSelectionScope else darkSelectionScope,
content = content
)
}
@Composable
fun JBDraculaTheme(content: @Composable () -> Unit) {
JBTheme(
style = JBThemeStyle.DARK,
buttonColors = darkButtonColors(),
fieldColors = darkFieldColors(),
focusColors = darkFocusColors(),
panelColors = darkPanelColors(),
textColors = darkTextColors(),
toolBarColors = darkToolBarColors(),
progressColors = darkProgressColors(),
scrollColors = darkScrollColors(),
tabColors = darkTabColors(),
selectionColors = darkSelectionColors(),
checkBoxColors = darkCheckBoxColors(),
iconColors = darkIconColors(),
typography = JBTypography(),
iconTheme = JBThemeStyle.DARK,
selectionScope = darkSelectionScope,
content = content
)
}
@Composable
fun JBLightTheme(content: @Composable () -> Unit) {
JBTheme(content = content)
}
@Composable
fun JBTheme(
style: JBThemeStyle = JBThemeStyle.LIGHT,
buttonColors: ButtonColors = lightButtonColors(),
fieldColors: FieldColors = lightFieldColors(),
focusColors: FocusColors = lightFocusColors(),
panelColors: PanelColors = lightPanelColors(),
textColors: TextColors = lightTextColors(),
toolBarColors: ToolBarColors = lightToolBarColors(),
progressColors: ProgressColors = lightProgressColors(),
scrollColors: ScrollColors = lightScrollColors(),
tabColors: TabColors = lightTabColors(),
selectionColors: SelectionColors = lightSelectionColors(),
checkBoxColors: CheckBoxColors = lightCheckBoxColors(),
iconColors: IconColors = lightIconColors(),
typography: JBTypography = JBTypography(),
iconTheme: JBThemeStyle = JBThemeStyle.LIGHT,
selectionScope: @Composable () -> Array<ProvidedValue<out Any>> = lightSelectionScope,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalThemeStyle provides style,
LocalButtonColors provides buttonColors,
LocalFieldColors provides fieldColors,
LocalFocusColors provides focusColors,
LocalPanelColors provides panelColors,
LocalTextColors provides textColors,
LocalToolBarColors provides toolBarColors,
LocalProgressColors provides progressColors,
LocalScrollColors provides scrollColors,
LocalTabColors provides tabColors,
LocalSelectionColors provides selectionColors,
LocalCheckBoxColors provides checkBoxColors,
LocalIconColors provides iconColors,
LocalTypography provides typography,
LocalIconTheme provides iconTheme,
LocalContentColor provides Color.Unspecified,
LocalSelectionScope provides selectionScope,
LocalScrollbarStyle provides scrollColors.style()
) {
ProvideTextStyle(value = typography.default, content = content)
}
}
object JBTheme {
val style: JBThemeStyle
@Composable @ReadOnlyComposable get() = LocalThemeStyle.current
val buttonColors: ButtonColors
@Composable @ReadOnlyComposable get() = LocalButtonColors.current
val fieldColors: FieldColors
@Composable @ReadOnlyComposable get() = LocalFieldColors.current
val focusColors: FocusColors
@Composable @ReadOnlyComposable get() = LocalFocusColors.current
val panelColors: PanelColors
@Composable @ReadOnlyComposable get() = LocalPanelColors.current
val textColors: TextColors
@Composable @ReadOnlyComposable get() = LocalTextColors.current
val toolBarColors: ToolBarColors
@Composable @ReadOnlyComposable get() = LocalToolBarColors.current
val progressColors: ProgressColors
@Composable @ReadOnlyComposable get() = LocalProgressColors.current
val scrollColors: ScrollColors
@Composable @ReadOnlyComposable get() = LocalScrollColors.current
val tabColors: TabColors
@Composable @ReadOnlyComposable get() = LocalTabColors.current
val selectionColors: SelectionColors
@Composable @ReadOnlyComposable get() = LocalSelectionColors.current
val checkBoxColors: CheckBoxColors
@Composable @ReadOnlyComposable get() = LocalCheckBoxColors.current
val iconColors: IconColors
@Composable @ReadOnlyComposable get() = LocalIconColors.current
val typography: JBTypography
@Composable @ReadOnlyComposable get() = LocalTypography.current
val iconTheme: JBThemeStyle
@Composable @ReadOnlyComposable get() = LocalIconTheme.current
}
enum class JBThemeStyle {
LIGHT, DARK
}
val LocalThemeStyle = compositionLocalOf { JBThemeStyle.LIGHT }
val LocalIconTheme = compositionLocalOf { JBThemeStyle.LIGHT }
val LocalSelectionScope = compositionLocalOf<@Composable () -> Array<ProvidedValue<out Any>>> {
lightSelectionScope
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/JBTypography.kt
================================================
package io.kanro.compose.jetbrains
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.sp
class JBTypography(
val h0: TextStyle,
val h1: TextStyle,
val h2: TextStyle,
val h2Bold: TextStyle,
val h3: TextStyle,
val h3Bold: TextStyle,
val default: TextStyle,
val defaultBold: TextStyle,
val defaultUnderlined: TextStyle,
val paragraph: TextStyle,
val medium: TextStyle,
val mediumBold: TextStyle,
val small: TextStyle,
val smallUnderlined: TextStyle,
) {
constructor(
defaultFontFamily: FontFamily = FontFamily.Default,
h0: TextStyle = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 25.sp
),
h1: TextStyle = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 22.sp
),
h2: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 18.sp,
),
h2Bold: TextStyle = h2.copy(fontWeight = FontWeight.Medium),
h3: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 20.sp
),
h3Bold: TextStyle = h3.copy(fontWeight = FontWeight.Medium),
default: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.5.sp,
lineHeight = 15.sp
),
defaultBold: TextStyle = default.copy(fontWeight = FontWeight.Medium),
defaultUnderlined: TextStyle = default.copy(textDecoration = TextDecoration.Underline),
paragraph: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 13.sp,
lineHeight = 19.sp
),
medium: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 15.sp
),
mediumBold: TextStyle = medium.copy(fontWeight = FontWeight.Medium),
small: TextStyle = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
lineHeight = 14.sp
),
smallUnderlined: TextStyle = small.copy(textDecoration = TextDecoration.Underline),
) : this(
h0.withDefaultFontFamily(defaultFontFamily),
h1.withDefaultFontFamily(defaultFontFamily),
h2.withDefaultFontFamily(defaultFontFamily),
h2Bold.withDefaultFontFamily(defaultFontFamily),
h3.withDefaultFontFamily(defaultFontFamily),
h3Bold.withDefaultFontFamily(defaultFontFamily),
default.withDefaultFontFamily(defaultFontFamily),
defaultBold.withDefaultFontFamily(defaultFontFamily),
defaultUnderlined.withDefaultFontFamily(defaultFontFamily),
paragraph.withDefaultFontFamily(defaultFontFamily),
medium.withDefaultFontFamily(defaultFontFamily),
mediumBold.withDefaultFontFamily(defaultFontFamily),
small.withDefaultFontFamily(defaultFontFamily),
smallUnderlined.withDefaultFontFamily(defaultFontFamily)
)
companion object {
private fun TextStyle.withDefaultFontFamily(default: FontFamily): TextStyle {
return if (fontFamily != null) this else copy(fontFamily = default)
}
}
}
val LocalTypography = compositionLocalOf { JBTypography() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ButtonColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import io.kanro.compose.jetbrains.JBTheme
class ButtonColors(
bg: Color,
border: Color,
borderRegularFocused: Color,
defaultStart: Color,
defaultEnd: Color,
borderDefaultStart: Color,
borderDefaultEnd: Color,
borderDefaultFocused: Color,
bgDisabled: Color,
borderDisabled: Color,
) {
var bg by mutableStateOf(bg)
var border by mutableStateOf(border)
var borderRegularFocused by mutableStateOf(borderRegularFocused)
var defaultStart by mutableStateOf(defaultStart)
var defaultEnd by mutableStateOf(defaultEnd)
var borderDefaultStart by mutableStateOf(borderDefaultStart)
var borderDefaultEnd by mutableStateOf(borderDefaultEnd)
var borderDefaultFocused by mutableStateOf(borderDefaultFocused)
var bgDisabled by mutableStateOf(bgDisabled)
var borderDisabled by mutableStateOf(borderDisabled)
fun copy(
bg: Color = this.bg,
border: Color = this.border,
borderRegularFocused: Color = this.borderRegularFocused,
defaultStart: Color = this.defaultStart,
defaultEnd: Color = this.defaultEnd,
borderDefaultStart: Color = this.borderDefaultStart,
borderDefaultEnd: Color = this.borderDefaultEnd,
borderDefaultFocused: Color = this.borderDefaultFocused,
disabled: Color = this.bgDisabled,
borderDisabled: Color = this.borderDisabled,
): ButtonColors {
return ButtonColors(
bg,
border,
borderRegularFocused,
defaultStart,
defaultEnd,
borderDefaultStart,
borderDefaultEnd,
borderDefaultFocused,
disabled,
borderDisabled
)
}
@Composable
private fun bgStartColor(enable: Boolean, default: Boolean): State<Color> {
val targetValue = when {
!enable -> bgDisabled
default -> defaultStart
else -> bg
}
return rememberUpdatedState(targetValue)
}
@Composable
private fun bgEndColor(enable: Boolean, default: Boolean): State<Color> {
val targetValue = when {
!enable -> bgDisabled
default -> defaultStart
else -> bg
}
return rememberUpdatedState(targetValue)
}
@Composable
fun bgBrush(enable: Boolean, default: Boolean): State<Brush> {
val start by bgStartColor(enable, default)
val end by bgEndColor(enable, default)
val targetValue = if (start == end) {
SolidColor(start)
} else {
Brush.linearGradient(listOf(start, end))
}
return rememberUpdatedState(targetValue)
}
@Composable
private fun borderStartColor(enable: Boolean, default: Boolean, focused: Boolean): State<Color> {
val targetValue = when {
!enable -> borderDisabled
default && focused -> borderDefaultFocused
default -> borderDefaultStart
focused -> borderRegularFocused
else -> border
}
return rememberUpdatedState(targetValue)
}
@Composable
private fun borderEndColor(enable: Boolean, default: Boolean, focused: Boolean): State<Color> {
val targetValue = when {
!enable -> borderDisabled
default && focused -> borderDefaultFocused
default -> borderDefaultEnd
focused -> borderRegularFocused
else -> border
}
return rememberUpdatedState(targetValue)
}
@Composable
fun borderBrush(enable: Boolean, default: Boolean, interactionSource: InteractionSource): State<Brush> {
val focused by interactionSource.collectIsFocusedAsState()
val start by borderStartColor(enable, default, focused)
val end by borderEndColor(enable, default, focused)
val targetValue = if (start == end) {
SolidColor(start)
} else {
Brush.linearGradient(listOf(start, end))
}
return rememberUpdatedState(targetValue)
}
@Composable
fun textColor(enable: Boolean, default: Boolean): State<Color> {
val start by bgStartColor(enable, default)
val targetValue = if ((start.green + start.blue + start.red) / 3.0 > 0.6) {
JBTheme.textColors.default
} else {
JBTheme.textColors.white
}
return rememberUpdatedState(targetValue)
}
@Composable
fun focusingColor(enable: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enable -> Color.Transparent
focused -> JBTheme.focusColors.default
else -> Color.Transparent
}
return rememberUpdatedState(targetValue)
}
}
fun lightButtonColors(): ButtonColors {
return ButtonColors(
Color.White,
Color(0xFFC4C4C4),
Color(0xFF87AFDA),
Color(0xFF528CC7), Color(0xFF4989CC),
Color(0xFF487EB8), Color(0xFF346DAD),
Color(0xFFA8CEF6),
Color(0xFFF2F2F2),
Color(0xFFCFCFCF),
)
}
fun darkButtonColors(): ButtonColors {
return ButtonColors(
Color(0xFF4C5052),
Color(0xFF5E6060),
Color(0xFF456A90),
Color(0xFF365880), Color(0xFF365880),
Color(0xFF4C708C), Color(0xFF4C708C),
Color(0xFF537699),
Color(0xFF3C3F41),
Color(0xFF646464),
)
}
val LocalButtonColors = compositionLocalOf { lightButtonColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/CheckBoxColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class CheckBoxColors(
bg: Color,
bgSelected: Color,
bgDisabled: Color,
border: Color,
borderSelected: Color,
borderFocused: Color,
borderDisabled: Color,
) {
var bg by mutableStateOf(bg)
var bgSelected by mutableStateOf(bgSelected)
var bgDisabled by mutableStateOf(bgDisabled)
var border by mutableStateOf(border)
var borderSelected by mutableStateOf(borderSelected)
var borderFocused by mutableStateOf(borderFocused)
var borderDisabled by mutableStateOf(borderDisabled)
fun copy(
bg: Color = this.bg,
bgSelected: Color = this.bgSelected,
bgDisabled: Color = this.bgDisabled,
border: Color = this.border,
borderSelected: Color = this.borderSelected,
borderFocused: Color = this.borderFocused,
borderDisabled: Color = this.borderDisabled,
): CheckBoxColors {
return CheckBoxColors(
bg,
bgSelected,
bgDisabled,
border,
borderSelected,
borderFocused,
borderDisabled
)
}
}
fun lightCheckBoxColors(): CheckBoxColors {
return CheckBoxColors(
Color.White,
Color(0xFF4F9EE3),
Color(0xFFF2F2F2),
Color(0xFFB0B0B0),
Color(0xFF4B97D9),
Color(0xFF7B9FC7),
Color(0xFFBDBDBD),
)
}
fun darkCheckBoxColors(): CheckBoxColors {
return CheckBoxColors(
Color(0xFF43494A),
Color(0xFF43494A),
Color(0xFF3C3F41),
Color(0xFF6B6B6B),
Color(0xFF4C708C),
Color(0xFF43698E),
Color(0xFF545556),
)
}
val LocalCheckBoxColors = compositionLocalOf { lightCheckBoxColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FieldColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import io.kanro.compose.jetbrains.JBTheme
class FieldColors(
bg: Color,
border: Color,
borderFocused: Color,
comboboxButton: Color,
bgDisabled: Color,
borderDisabled: Color,
borderError: Color,
) {
var bg by mutableStateOf(bg)
var border by mutableStateOf(border)
var borderFocused by mutableStateOf(borderFocused)
var comboboxButton by mutableStateOf(comboboxButton)
var bgDisabled by mutableStateOf(bgDisabled)
var borderDisabled by mutableStateOf(borderDisabled)
var borderError by mutableStateOf(borderError)
fun copy(
bg: Color = this.bg,
border: Color = this.border,
borderFocused: Color = this.borderFocused,
comboboxButton: Color = this.comboboxButton,
bgDisabled: Color = this.bgDisabled,
borderDisabled: Color = this.borderDisabled,
borderError: Color = this.borderError,
): FieldColors {
return FieldColors(bg, border, borderFocused, comboboxButton, bgDisabled, borderDisabled, borderError)
}
@Composable
fun bgColor(enable: Boolean): State<Color> {
return rememberUpdatedState(if (enable) bg else bgDisabled)
}
@Composable
fun borderColor(enable: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enable -> borderDisabled
error -> borderError
focused -> borderFocused
else -> border
}
return if (enable) {
animateColorAsState(targetValue, tween(durationMillis = AnimationDuration))
} else {
rememberUpdatedState(targetValue)
}
}
@Composable
fun focusingColor(enable: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enable -> Color.Transparent
error -> JBTheme.focusColors.error
focused -> JBTheme.focusColors.default
else -> Color.Transparent
}
return rememberUpdatedState(targetValue)
}
companion object {
internal const val AnimationDuration = 150
}
}
fun lightFieldColors(): FieldColors {
return FieldColors(
Color.White,
Color(0xFFC4C4C4),
Color(0xFF87AFDA),
Color(0xFFFAFAFA),
Color(0xFFF2F2F2),
Color(0xFFCFCFCF),
Color(0xFFCE3845),
)
}
fun darkFieldColors(): FieldColors {
return FieldColors(
Color(0xFF4C5052),
Color(0xFF5E6060),
Color(0xFF456A90),
Color(0xFF3C3F41),
Color(0xFF3C3F41),
Color(0xFF646464),
Color(0xFF73454B),
)
}
val LocalFieldColors = compositionLocalOf { lightFieldColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FocusColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class FocusColors(
default: Color,
error: Color,
warning: Color,
warningInactive: Color,
) {
var default by mutableStateOf(default)
var error by mutableStateOf(error)
var warning by mutableStateOf(warning)
var warningInactive by mutableStateOf(warningInactive)
fun copy(
default: Color = this.default,
error: Color = this.error,
warning: Color = this.warning,
warningInactive: Color = this.warningInactive,
): FocusColors {
return FocusColors(default, error, warning, warningInactive)
}
}
fun lightFocusColors(): FocusColors {
return FocusColors(
Color(0xFF97C3F3),
Color(0xFFE53E4D),
Color(0xFFE1A336),
Color(0xFFEAD2A1),
)
}
fun darkFocusColors(): FocusColors {
return FocusColors(
Color(0xFF3D6185),
Color(0xFF8B3C3C),
Color(0xFFAC7920),
Color(0xFF6E5324),
)
}
val LocalFocusColors = compositionLocalOf { lightFocusColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/IconColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class IconColors(
selected: Color,
disabled: Color,
) {
var selected by mutableStateOf(selected)
var disabled by mutableStateOf(disabled)
fun copy(
selected: Color = this.selected,
disabled: Color = this.disabled,
): IconColors {
return IconColors(
selected,
disabled,
)
}
}
fun lightIconColors(): IconColors {
return IconColors(
Color.White,
Color(0xFFABABAB),
)
}
fun darkIconColors(): IconColors {
return IconColors(
Color(0xFFFEFEFE),
Color(0xFFABABAB),
)
}
val LocalIconColors = compositionLocalOf { lightIconColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/PanelColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class PanelColors(
border: Color,
bgContent: Color,
bgDialog: Color,
) {
var border by mutableStateOf(border)
var bgContent by mutableStateOf(bgContent)
var bgDialog by mutableStateOf(bgDialog)
fun copy(
border: Color = this.border,
bgContent: Color = this.bgContent,
bgDialog: Color = this.bgDialog,
): PanelColors {
return PanelColors(border, bgContent, bgDialog)
}
}
fun lightPanelColors(): PanelColors {
return PanelColors(
Color(0xFFD1D1D1),
Color.White,
Color(0xFFF2F2F2),
)
}
fun darkPanelColors(): PanelColors {
return PanelColors(
Color(0xFF323232),
Color(0xFF3C3F41),
Color(0xFF3C3F41),
)
}
val LocalPanelColors = compositionLocalOf { lightPanelColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ProgressColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class ProgressColors(
progress: Color,
bg: Color,
) {
var progress by mutableStateOf(progress)
var bg by mutableStateOf(bg)
fun copy(
progress: Color = this.progress,
bg: Color = this.bg,
): ProgressColors {
return ProgressColors(progress, bg)
}
}
fun lightProgressColors(): ProgressColors {
return ProgressColors(
Color(0xFF1E82E6),
Color(0xFFD5D5D5),
)
}
fun darkProgressColors(): ProgressColors {
return ProgressColors(
Color(0xFFA0A0A0),
Color(0xFF555555),
)
}
val LocalProgressColors = compositionLocalOf { lightProgressColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ScrollColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.foundation.ScrollbarStyle
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
class ScrollColors(
bg: Color,
) {
var bg by mutableStateOf(bg)
fun copy(
bg: Color = this.bg,
): ScrollColors {
return ScrollColors(bg)
}
fun style(): ScrollbarStyle {
return ScrollbarStyle(
minimalHeight = 30.dp,
thickness = 7.dp,
shape = CircleShape,
hoverDurationMillis = 0,
unhoverColor = bg,
hoverColor = bg
)
}
}
fun lightScrollColors(): ScrollColors {
return ScrollColors(
Color(0xFFC9C9C9),
)
}
fun darkScrollColors(): ScrollColors {
return ScrollColors(
Color(0xFF494949)
)
}
val LocalScrollColors = compositionLocalOf { lightScrollColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/SelectionColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class SelectionColors(
active: Color,
inactive: Color,
hover: Color,
lightActive: Color,
lightInactive: Color,
completionPopup: Color,
) {
var active by mutableStateOf(active)
var inactive by mutableStateOf(inactive)
var hover by mutableStateOf(hover)
var lightActive by mutableStateOf(lightActive)
var lightInactive by mutableStateOf(lightInactive)
var completionPopup by mutableStateOf(completionPopup)
fun copy(
active: Color = this.active,
inactive: Color = this.inactive,
hover: Color = this.hover,
lightActive: Color = this.lightActive,
lightInactive: Color = this.lightInactive,
completionPopup: Color = this.completionPopup,
): SelectionColors {
return SelectionColors(active, inactive, hover, lightActive, lightInactive, completionPopup)
}
}
fun lightSelectionColors(): SelectionColors {
return SelectionColors(
Color(0xFF2675BF),
Color(0xFFD5D5D5),
Color(0xFFEDF5FC),
Color(0xFFEDF6FE),
Color(0xFFF5F5F5),
Color(0xFFC5DFFC),
)
}
fun darkSelectionColors(): SelectionColors {
return SelectionColors(
Color(0xFF2F65CA),
Color(0xFF0D293E),
Color(0xFF464A4D),
Color(0xFF464A4D),
Color(0xFF35383B),
Color(0xFF113A5C),
)
}
val LocalSelectionColors = compositionLocalOf { lightSelectionColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TabColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class TabColors(
selection: Color,
focus: Color,
selectionInactive: Color,
hover: Color,
selectionDisabled: Color,
bgSelected: Color,
) {
var selection by mutableStateOf(selection)
var focus by mutableStateOf(focus)
var selectionInactive by mutableStateOf(selectionInactive)
var hover by mutableStateOf(hover)
var selectionDisabled by mutableStateOf(selectionDisabled)
var bgSelected by mutableStateOf(bgSelected)
fun copy(
selection: Color = this.selection,
focus: Color = this.focus,
selectionInactive: Color = this.selectionInactive,
hover: Color = this.hover,
selectionDisabled: Color = this.selectionDisabled,
bgSelected: Color = this.bgSelected,
): TabColors {
return TabColors(selection, focus, selectionInactive, hover, selectionDisabled, bgSelected)
}
}
fun lightTabColors(): TabColors {
return TabColors(
Color(0xFF4083C9),
Color(0xFFDAE4ED),
Color(0xFF9CA7B8),
Color(0x19000000),
Color(0xFFABABAB),
Color(0xFFFFFFFF),
)
}
fun darkTabColors(): TabColors {
return TabColors(
Color(0xFF4A88C7),
Color(0xFF3D4B5C),
Color(0xFF747A80),
Color(0xFF2E3133),
Color(0xFF595959),
Color(0xFF4E5254),
)
}
val LocalTabColors = compositionLocalOf { lightTabColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TableColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class TableColors(
bg: Color,
headerBorder: Color,
outerBorder: Color,
) {
var bg by mutableStateOf(bg)
var headerBorder by mutableStateOf(headerBorder)
var outerBorder by mutableStateOf(outerBorder)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TextColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class TextColors(
default: Color,
disabled: Color,
white: Color,
link: Color,
infoPanel: Color,
infoInput: Color,
error: Color,
success: Color,
) {
var default by mutableStateOf(default)
var disabled by mutableStateOf(disabled)
var white by mutableStateOf(white)
var link by mutableStateOf(link)
var infoPanel by mutableStateOf(infoPanel)
var infoInput by mutableStateOf(infoInput)
var error by mutableStateOf(error)
var success by mutableStateOf(success)
fun copy(
default: Color = this.default,
disabled: Color = this.disabled,
white: Color = this.white,
link: Color = this.link,
infoPanel: Color = this.infoPanel,
infoInput: Color = this.infoInput,
error: Color = this.error,
success: Color = this.success,
): TextColors {
return TextColors(default, disabled, white, link, infoPanel, infoInput, error, success)
}
}
fun lightTextColors(): TextColors {
return TextColors(
Color.Black,
Color(0xFF8C8C8C),
Color.White,
Color(0xFF2470B3),
Color(0xFF808080),
Color(0xFF999999),
Color(0xFFC7222D),
Color(0xFF368746),
)
}
fun darkTextColors(): TextColors {
return TextColors(
Color(0xFFBBBBBB),
Color(0xFF777777),
Color(0xFFFEFEFE),
Color(0xFF589DF6),
Color(0xFF8C8C8C),
Color(0xFF787878),
Color(0xFFFF5261),
Color(0xFF50A661),
)
}
val LocalTextColors = compositionLocalOf { lightTextColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToggleColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class ToggleColors(
bg: Color,
off: Color,
on: Color,
) {
var bg by mutableStateOf(bg)
var off by mutableStateOf(off)
var on by mutableStateOf(on)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToolBarColors.kt
================================================
package io.kanro.compose.jetbrains.color
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
class ToolBarColors(
buttonPressed: Color,
buttonHover: Color,
iconSplitBorder: Color,
) {
var buttonPressed by mutableStateOf(buttonPressed)
var buttonHover by mutableStateOf(buttonHover)
var iconSplitBorder by mutableStateOf(iconSplitBorder)
fun copy(
buttonPressed: Color = this.buttonPressed,
buttonHover: Color = this.buttonHover,
iconSplitBorder: Color = this.iconSplitBorder,
): ToolBarColors {
return ToolBarColors(buttonPressed, buttonHover, iconSplitBorder)
}
@Composable
fun actionIconBgColor(interactionSource: InteractionSource): State<Color> {
val pressed by interactionSource.collectIsPressedAsState()
val hover by interactionSource.collectIsHoveredAsState()
val targetValue = when {
pressed -> buttonPressed
hover -> buttonHover
else -> Color.Transparent
}
return rememberUpdatedState(targetValue)
}
}
fun lightToolBarColors(): ToolBarColors {
return ToolBarColors(
Color(0xFFCFCFCF),
Color(0xFFDFDFDF),
Color(0xFFB3B3B3),
)
}
fun darkToolBarColors(): ToolBarColors {
return ToolBarColors(
Color(0xFF5C6164),
Color(0xFF4C5052),
Color(0xFF6B6B6B),
)
}
val LocalToolBarColors = compositionLocalOf { lightToolBarColors() }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ActionButton.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
object ActionButtonIndication : Indication {
private class ActionButtonIndicationInstance(
private val shape: Shape,
private val isHover: State<Boolean>,
private val isPressed: State<Boolean>,
private val hoverColor: Color,
private val pressedColor: Color,
) : IndicationInstance {
override fun ContentDrawScope.drawIndication() {
when {
isPressed.value -> {
val outline = shape.createOutline(size, layoutDirection, this)
drawOutline(outline, pressedColor)
}
isHover.value -> {
val outline = shape.createOutline(size, layoutDirection, this)
drawOutline(outline, hoverColor)
}
}
drawContent()
}
}
@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val shape = remember { RoundedCornerShape(3.dp) }
val isPressed = interactionSource.collectIsPressedAsState()
val isHover = interactionSource.collectIsHoveredAsState()
val hoverColor = JBTheme.toolBarColors.buttonHover
val pressedColor = JBTheme.toolBarColors.buttonPressed
return remember(JBTheme.toolBarColors, interactionSource) {
ActionButtonIndicationInstance(
shape,
isHover,
isPressed,
hoverColor,
pressedColor
)
}
}
}
object ActionButtonDefaults {
private val ButtonHorizontalPadding = 3.dp
private val ButtonVerticalPadding = 3.dp
val MinWidth = 22.dp
val MinHeight = 22.dp
val IconSize = 16.dp
val ContentPadding = PaddingValues(
start = ButtonHorizontalPadding,
top = ButtonVerticalPadding,
end = ButtonHorizontalPadding,
bottom = ButtonVerticalPadding
)
}
@Composable
fun ActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
background: Color = Color.Transparent,
indication: Indication? = ActionButtonIndication,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RoundedCornerShape(3.dp),
border: BorderStroke? = null,
contentPadding: PaddingValues = ActionButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit,
) {
Box(
modifier
.then(if (border != null) Modifier.border(border, shape) else Modifier)
.background(
color = background,
shape = shape
)
.clip(shape)
.clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClick = onClick
)
.hoverable(interactionSource, enabled),
propagateMinConstraints = true
) {
CompositionLocalProvider(
LocalContentColor provides (if (!enabled) JBTheme.iconColors.disabled else LocalContentColor.current)
) {
Row(
Modifier
.defaultMinSize(
minWidth = ActionButtonDefaults.MinWidth,
minHeight = ActionButtonDefaults.MinHeight
)
.padding(contentPadding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content
)
}
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Button.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
import io.kanro.compose.jetbrains.JBTheme
@Stable
interface ButtonStyle {
@Composable
fun borderBrush(enabled: Boolean): State<Brush>
@Composable
fun backgroundBrush(enabled: Boolean): State<Brush>
@Composable
fun contentColor(enabled: Boolean): State<Color>
@Composable
fun focusingColor(enabled: Boolean, interactionSource: InteractionSource): State<Color>
}
private data class DefaultButtonStyle(
private val borderBrush: Brush,
private val backgroundBrush: Brush,
private val contentColor: Color,
private val disableBorderBrush: Brush,
private val disableBackgroundBrush: Brush,
private val disabledContentColor: Color,
private val focusingColor: Color,
) : ButtonStyle {
@Composable
override fun borderBrush(enabled: Boolean): State<Brush> {
return rememberUpdatedState(if (enabled) borderBrush else disableBorderBrush)
}
@Composable
override fun backgroundBrush(enabled: Boolean): State<Brush> {
return rememberUpdatedState(if (enabled) backgroundBrush else disableBackgroundBrush)
}
@Composable
override fun contentColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(if (enabled) contentColor else disabledContentColor)
}
@Composable
override fun focusingColor(enabled: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
return rememberUpdatedState(if (enabled && focused) focusingColor else Color.Transparent)
}
}
object ButtonDefaults {
private val ButtonHorizontalPadding = 14.dp
private val ButtonVerticalPadding = 3.dp
val ContentPadding = PaddingValues(
start = ButtonHorizontalPadding,
top = ButtonVerticalPadding,
end = ButtonHorizontalPadding,
bottom = ButtonVerticalPadding
)
val MinWidth = 72.dp
val MinHeight = 24.dp
val IconSize = 16.dp
val IconSpacing = 8.dp
@Composable
fun buttonStyle(
borderBrush: Brush = Brush.verticalGradient(
listOf(
JBTheme.buttonColors.borderDefaultStart,
JBTheme.buttonColors.borderDefaultEnd
)
),
backgroundBrush: Brush = Brush.verticalGradient(
listOf(
JBTheme.buttonColors.defaultStart,
JBTheme.buttonColors.defaultEnd
)
),
contentColor: Color = JBTheme.contentColorFor(JBTheme.buttonColors.defaultEnd),
disableBorderBrush: Brush = SolidColor(JBTheme.buttonColors.borderDisabled),
disableBackgroundBrush: Brush = SolidColor(JBTheme.buttonColors.bgDisabled),
disabledContentColor: Color = JBTheme.textColors.disabled,
focusingColor: Color = JBTheme.focusColors.default,
): ButtonStyle = DefaultButtonStyle(
borderBrush,
backgroundBrush,
contentColor,
disableBorderBrush,
disableBackgroundBrush,
disabledContentColor,
focusingColor
)
@Composable
fun outlineButtonStyle(
borderBrush: Brush = SolidColor(JBTheme.buttonColors.border),
backgroundBrush: Brush = SolidColor(JBTheme.buttonColors.bg),
contentColor: Color = JBTheme.contentColorFor(JBTheme.buttonColors.bg),
disableBorderBrush: Brush = SolidColor(JBTheme.buttonColors.borderDisabled),
disableBackgroundBrush: Brush = SolidColor(JBTheme.buttonColors.bgDisabled),
disabledContentColor: Color = JBTheme.textColors.disabled,
focusingColor: Color = JBTheme.focusColors.default,
): ButtonStyle = DefaultButtonStyle(
borderBrush,
backgroundBrush,
contentColor,
disableBorderBrush,
disableBackgroundBrush,
disabledContentColor,
focusingColor
)
}
@Composable
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RoundedCornerShape(3.dp),
borderWidth: Dp = 1.dp,
style: ButtonStyle = ButtonDefaults.buttonStyle(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit,
) {
val focusRequester = remember { FocusRequester() }
Box(
modifier
.then(
if (borderWidth.isSpecified) Modifier.border(
borderWidth,
style.borderBrush(enabled).value,
shape
) else Modifier
)
.background(
brush = style.backgroundBrush(enabled).value,
shape = shape
)
.focusRequester(focusRequester)
.focusable(enabled, interactionSource)
.clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = onClick
)
.pointerInput("Fo") {
this.awaitPointerEventScope {
var event: PointerEvent
do {
event = awaitPointerEvent()
if (event.changes.any { it.changedToDownIgnoreConsumed() }) {
focusRequester.requestFocus()
}
} while (true)
}
}
.buttonIndicator(style.focusingColor(enabled, interactionSource).value, shape, 2.dp, 5.dp),
propagateMinConstraints = true
) {
val contentColor by style.contentColor(enabled)
CompositionLocalProvider(
LocalContentAlpha provides contentColor.alpha,
LocalContentColor provides contentColor
) {
ProvideTextStyle(JBTheme.typography.defaultBold) {
Row(
Modifier
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = ButtonDefaults.MinHeight
)
.padding(contentPadding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content
)
}
}
}
}
private fun Modifier.buttonIndicator(color: Color, shape: Shape, width: Dp, cornerRadius: Dp): Modifier {
if (color.alpha == 0f) return this
return drawBehind {
val controlOutline = shape.createOutline(size, layoutDirection, this)
val highlightOutline = RoundRect(controlOutline.bounds.inflate(width.toPx()), CornerRadius(cornerRadius.toPx()))
val highlightPath = Path().apply {
this.fillType = PathFillType.EvenOdd
addRoundRect(highlightOutline)
addOutline(controlOutline)
close()
}
drawPath(highlightPath, color)
}
}
@Composable
fun OutlineButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RoundedCornerShape(3.dp),
borderWidth: Dp = 1.dp,
style: ButtonStyle = ButtonDefaults.outlineButtonStyle(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit,
) = Button(onClick, modifier, enabled, interactionSource, shape, borderWidth, style, contentPadding, content)
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/CheckBox.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
import io.kanro.compose.jetbrains.icons.JBIcons
import io.kanro.compose.jetbrains.icons.jbicons.Actions
import io.kanro.compose.jetbrains.icons.jbicons.actions.Checkmark
import io.kanro.compose.jetbrains.icons.jbicons.actions.CheckmarkIndeterminate
@Composable
fun CheckBox(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
TriStateCheckbox(
state = ToggleableState(checked),
onClick = if (onCheckedChange != null) {
{ onCheckedChange(!checked) }
} else null,
interactionSource = interactionSource,
enabled = enabled,
modifier = modifier
)
}
@Composable
fun TriStateCheckbox(
state: ToggleableState,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val toggleableModifier =
if (onClick != null) {
Modifier.triStateToggleable(
state = state,
onClick = onClick,
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = null
)
} else {
Modifier
}
CheckboxImpl(
enabled = enabled,
value = state,
modifier = modifier
.then(toggleableModifier),
)
}
@Composable
private fun CheckboxImpl(
enabled: Boolean,
value: ToggleableState,
modifier: Modifier,
) {
val icon = when (value) {
ToggleableState.On -> rememberVectorPainter(JBIcons.Actions.Checkmark)
ToggleableState.Indeterminate -> rememberVectorPainter(JBIcons.Actions.CheckmarkIndeterminate)
else -> null
}
val iconFilter = if (!enabled) {
ColorFilter.tint(JBTheme.iconColors.disabled, BlendMode.DstIn)
} else null
val bg = if (enabled) {
when (value) {
ToggleableState.On, ToggleableState.Indeterminate -> JBTheme.checkBoxColors.bgSelected
ToggleableState.Off -> JBTheme.checkBoxColors.bg
}
} else JBTheme.checkBoxColors.bgDisabled
val border = if (enabled) {
when (value) {
ToggleableState.On, ToggleableState.Indeterminate -> JBTheme.checkBoxColors.borderSelected
ToggleableState.Off -> JBTheme.checkBoxColors.border
}
} else JBTheme.checkBoxColors.borderDisabled
Canvas(modifier.wrapContentSize(Alignment.Center).requiredSize(14.dp)) {
drawRoundRect(border, cornerRadius = CornerRadius(2.dp.toPx()))
drawRoundRect(
bg,
size = Size(12.dp.toPx(), 12.dp.toPx()),
topLeft = Offset(1.dp.toPx(), 1.dp.toPx()),
cornerRadius = CornerRadius(1.dp.toPx())
)
if (icon != null) {
with(icon) {
14.dp.toPx()
draw(Size(14.dp.toPx(), 14.dp.toPx()), colorFilter = iconFilter)
}
}
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ComboBox.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBThemeStyle
import io.kanro.compose.jetbrains.LocalIconTheme
import io.kanro.compose.jetbrains.icons.JBIcons
import io.kanro.compose.jetbrains.icons.jbicons.General
import io.kanro.compose.jetbrains.icons.jbicons.general.ButtonDropTriangle
import io.kanro.compose.jetbrains.icons.jbicons.general.ButtonDropTriangleDark
@Composable
fun <T> DropdownList(
items: List<T>,
value: T,
onValueChange: ((T) -> Unit)? = null,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRender: (T) -> String = { it.toString() },
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
) {
val shape = RoundedCornerShape(3.dp)
val focusRequester = remember { FocusRequester() }
val expanded = remember { mutableStateOf(false) }
Box(
modifier.border(1.dp, style.borderColor(enabled, false, interactionSource).value, shape).height(24.dp)
.background(style.backgroundColor(enabled, interactionSource).value).focusable(enabled, interactionSource)
.focusRequester(focusRequester)
.clickable(interactionSource = interactionSource, indication = null, enabled = enabled, onClick = {
expanded.value = true
focusRequester.requestFocus()
}).comboBoxIndicator(style.indicatorColor(false, interactionSource).value, shape, 2.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(6.dp))
Text(
valueRender(value),
Modifier.defaultMinSize(50.dp),
color = style.textColor(enabled, false, interactionSource).value
)
Spacer(Modifier.width(1.dp).fillMaxHeight())
Box(Modifier.size(22.dp), contentAlignment = Alignment.Center) {
val isDarkTheme = LocalIconTheme.current == JBThemeStyle.DARK
Icon(
imageVector = if (isDarkTheme) JBIcons.General.ButtonDropTriangleDark
else JBIcons.General.ButtonDropTriangle
)
}
}
DropdownMenu(expanded.value, offset = DpOffset(0.dp, 4.dp), onDismissRequest = {
expanded.value = false
}) {
items.forEach {
DropdownMenuItem(
modifier = Modifier.defaultMinSize(79.dp, 21.dp), onClick = {
expanded.value = false
onValueChange?.invoke(it)
}, enabled = enabled
) {
Text(valueRender(it), Modifier.padding(horizontal = 6.dp))
}
}
}
}
}
private fun Modifier.comboBoxIndicator(color: Color, shape: Shape, width: Dp): Modifier {
if (color.alpha == 0f) return this
return drawBehind {
val controlOutline = shape.createOutline(size, layoutDirection, this)
val highlightPath = Path().apply {
this.fillType = PathFillType.EvenOdd
addOutline(controlOutline)
val borderRect = controlOutline.bounds.inflate(width.toPx())
addRoundRect(RoundRect(borderRect, 4.dp.toPx(), 4.dp.toPx()))
close()
}
drawPath(highlightPath, color)
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentAlpha.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.runtime.compositionLocalOf
val LocalContentAlpha = compositionLocalOf { 1f }
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentColor.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import io.kanro.compose.jetbrains.JBTheme
val LocalContentColor = compositionLocalOf { Color.Unspecified }
@Composable
fun JBTheme.contentColorFor(backgroundColor: Color): Color {
return if ((backgroundColor.green + backgroundColor.blue + backgroundColor.red) / 3.0 > 0.6) {
textColors.default
} else {
textColors.white
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContextMenu.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.ContextMenuData
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ContextMenuRepresentation
import androidx.compose.foundation.ContextMenuState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isUnspecified
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.changedToDown
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.rememberCursorPositionProvider
import io.kanro.compose.jetbrains.JBTheme
private val LocalContextMenuData = staticCompositionLocalOf<ContextMenuData?> {
null
}
@Composable
internal fun ContextMenuDataProvider(
data: ContextMenuData,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalContextMenuData provides data
) {
content()
}
}
private suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
var event: PointerEvent
do {
event = awaitPointerEvent()
} while (!event.changes.all { it.changedToDown() })
return event
}
private fun Modifier.contextMenuDetector(
state: ContextMenuState,
enabled: Boolean = true,
): Modifier {
return if (enabled && state.status == ContextMenuState.Status.Closed) {
this.pointerInput(state) {
forEachGesture {
awaitPointerEventScope {
val event = awaitEventFirstDown()
if (event.buttons.isSecondaryPressed) {
event.changes.forEach { if (it.pressed != it.previousPressed) it.consume() }
state.status = ContextMenuState.Status.Open(Rect(event.changes[0].position, 0f))
}
}
}
}
} else {
Modifier
}
}
val LocalContextMenuRepresentation: ProvidableCompositionLocal<ContextMenuRepresentation> = staticCompositionLocalOf {
JBContextMenuRepresentation(Color.Unspecified, Color.Unspecified)
}
@Composable
fun ContextMenuArea(
items: () -> List<ContextMenuItem>,
state: ContextMenuState = remember { ContextMenuState() },
enabled: Boolean = true,
content: @Composable () -> Unit,
) {
val data = ContextMenuData(items, LocalContextMenuData.current)
ContextMenuDataProvider(data) {
Box(Modifier.contextMenuDetector(state, enabled), propagateMinConstraints = true) {
content()
}
LocalContextMenuRepresentation.current.Representation(state, items)
}
}
@OptIn(ExperimentalComposeUiApi::class)
class JBContextMenuRepresentation(
private val backgroundColor: Color,
private val borderColor: Color,
) : ContextMenuRepresentation {
@Composable
override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) {
val isOpen = state.status is ContextMenuState.Status.Open
if (isOpen) {
Popup(
focusable = true,
onDismissRequest = { state.status = ContextMenuState.Status.Closed },
popupPositionProvider = rememberCursorPositionProvider(),
onKeyEvent = {
if (it.key == Key.Escape) {
state.status = ContextMenuState.Status.Closed
true
} else {
false
}
},
) {
val backgroundColor = if (backgroundColor.isUnspecified) {
JBTheme.panelColors.bgContent
} else backgroundColor
val borderColor = if (borderColor.isUnspecified) {
JBTheme.panelColors.border
} else borderColor
Column(
Modifier.background(backgroundColor).border(1.dp, borderColor).width(IntrinsicSize.Max)
) {
items().forEach {
DropdownMenuItem(
modifier = Modifier.defaultMinSize(79.dp, 21.dp),
onClick = {
it.onClick()
state.status = ContextMenuState.Status.Closed
},
) {
Text(it.label, Modifier.padding(horizontal = 6.dp))
}
}
}
}
}
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/DropdownMenu.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import io.kanro.compose.jetbrains.JBTheme
import io.kanro.compose.jetbrains.color.LocalPanelColors
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
focusable: Boolean = true,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
content: @Composable ColumnScope.() -> Unit,
) {
if (expanded) {
val density = LocalDensity.current
val popupPositionProvider = DropdownMenuPositionProvider(
offset,
density
)
Popup(
focusable = focusable,
onDismissRequest = onDismissRequest,
popupPositionProvider = popupPositionProvider,
onKeyEvent = {
if (it.key == Key.Escape) {
onDismissRequest()
true
} else {
false
}
},
) {
val panelColor = LocalPanelColors.current
Column(modifier.background(panelColor.bgContent).border(1.dp, panelColor.border).width(IntrinsicSize.Max)) {
content()
}
}
}
}
@Composable
fun DropdownMenuItem(
modifier: Modifier = Modifier,
onClick: () -> Unit,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit,
) {
Box(
modifier
.clickable(
interactionSource = interactionSource,
indication = DropdownMenuItemHoverIndication,
enabled = enabled,
onClick = onClick
)
.fillMaxWidth()
.hoverable(interactionSource, enabled),
contentAlignment = Alignment.CenterStart
) {
val isHovered = interactionSource.collectIsHoveredAsState()
SelectionScope(isHovered.value) {
content()
}
}
}
object DropdownMenuItemHoverIndication : Indication {
private class HoverIndicationInstance(
private val isHover: State<Boolean>,
private val hoverColor: Color,
) : IndicationInstance {
override fun ContentDrawScope.drawIndication() {
when {
isHover.value -> {
drawRect(hoverColor, size = size)
}
}
drawContent()
}
}
@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val isHover = interactionSource.collectIsHoveredAsState()
val hoverColor = JBTheme.selectionColors.active
return remember(JBTheme.selectionColors, interactionSource) {
HoverIndicationInstance(
isHover,
hoverColor,
)
}
}
}
data class DropdownMenuPositionProvider(
val contentOffset: DpOffset,
val density: Density,
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> },
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize,
): IntOffset {
// The min margin above and below the menu, relative to the screen.
val verticalMargin = with(density) { 0 }
// The content offset specified using the dropdown offset parameter.
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
// Compute horizontal position.
val toRight = anchorBounds.left + contentOffsetX
val toLeft = anchorBounds.right - contentOffsetX - popupContentSize.width
val toDisplayRight = windowSize.width - popupContentSize.width
val toDisplayLeft = 0
val x = if (layoutDirection == LayoutDirection.Ltr) {
sequenceOf(toRight, toLeft, toDisplayRight)
} else {
sequenceOf(toLeft, toRight, toDisplayLeft)
}.firstOrNull {
it >= 0 && it + popupContentSize.width <= windowSize.width
} ?: toLeft
// Compute vertical position.
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
val toCenter = anchorBounds.top - popupContentSize.height / 2
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
var y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull {
it >= verticalMargin &&
it + popupContentSize.height <= windowSize.height - verticalMargin
} ?: toTop
// Desktop specific vertical position checking
val aboveAnchor = anchorBounds.top + contentOffsetY
val belowAnchor = windowSize.height - anchorBounds.bottom - contentOffsetY
if (belowAnchor >= aboveAnchor) {
y = anchorBounds.bottom + contentOffsetY
}
if (y + popupContentSize.height > windowSize.height) {
y = windowSize.height - popupContentSize.height
}
if (y < 0) {
y = 0
}
onPositionCalculated(
anchorBounds,
IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height)
)
return IntOffset(x, y)
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/EmbeddedTextField.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun EmbeddedTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = true,
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RectangleShape,
style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
) {
TextField(
value,
onValueChange,
modifier.embeddedTextFieldIndicator(style.indicatorColor(isError, interactionSource).value, shape, 2.dp),
enabled,
readOnly,
textStyle,
placeholder,
leadingIcon,
trailingIcon,
isError,
visualTransformation,
keyboardOptions,
keyboardActions,
singleLine,
maxLines,
interactionSource,
shape,
InnerStyle(style)
)
}
private class InnerStyle(val style: TextFieldStyle) : TextFieldStyle {
@Composable
override fun textColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
return if (focused) style.textColor(
enabled,
isError,
interactionSource
) else rememberUpdatedState(Color.Unspecified)
}
@Composable
override fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
return if (focused) style.backgroundColor(
enabled,
interactionSource
) else rememberUpdatedState(Color.Transparent)
}
@Composable
override fun placeholderColor(enabled: Boolean): State<Color> {
return style.placeholderColor(enabled)
}
@Composable
override fun borderColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color> {
return rememberUpdatedState(Color.Transparent)
}
@Composable
override fun indicatorColor(isError: Boolean, interactionSource: InteractionSource): State<Color> {
return rememberUpdatedState(Color.Transparent)
}
@Composable
override fun cursorColor(isError: Boolean): State<Color> {
return style.cursorColor(isError)
}
}
private fun Modifier.embeddedTextFieldIndicator(color: Color, shape: Shape, width: Dp): Modifier {
if (color.alpha == 0f) return this
return drawBehind {
val controlOutline = shape.createOutline(size, layoutDirection, this)
val highlightPath = Path().apply {
this.fillType = PathFillType.EvenOdd
addOutline(controlOutline)
addRect(controlOutline.bounds.deflate(width.toPx()))
close()
}
drawPath(highlightPath, color)
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Icon.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toolingGraphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBThemeStyle
import io.kanro.compose.jetbrains.LocalIconTheme
@Composable
fun Icon(
resource: String,
contentDescription: String? = null,
modifier: Modifier = Modifier,
colorFilter: ColorFilter? = null,
) {
Icon(
themedSvgResource(resource, LocalIconTheme.current), contentDescription, modifier,
colorFilter = colorFilter,
)
}
@Composable
fun Icon(
bitmap: ImageBitmap,
contentDescription: String? = null,
modifier: Modifier = Modifier,
colorFilter: ColorFilter? = null,
) {
val painter = remember(bitmap) { BitmapPainter(bitmap) }
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = modifier,
colorFilter = colorFilter,
)
}
@Composable
fun Icon(
imageVector: ImageVector,
contentDescription: String? = null,
modifier: Modifier = Modifier,
colorFilter: ColorFilter? = null,
) {
Icon(
painter = rememberVectorPainter(imageVector),
contentDescription = contentDescription,
modifier = modifier,
colorFilter = colorFilter,
)
}
@Composable
fun Icon(
painter: Painter,
contentDescription: String? = null,
modifier: Modifier = Modifier,
colorFilter: ColorFilter? = null,
) {
val semantics = if (contentDescription != null) {
Modifier.semantics {
this.contentDescription = contentDescription
this.role = Role.Image
}
} else {
Modifier
}
val filter = colorFilter ?: run {
if (LocalContentColor.current.isSpecified) {
ColorFilter.tint(LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
} else {
null
}
}
Box(
modifier.toolingGraphicsLayer().defaultSizeFor(painter)
.paint(
painter,
contentScale = ContentScale.None,
colorFilter = filter
)
.then(semantics)
)
}
@Composable
fun themedSvgResource(resource: String, theme: JBThemeStyle = LocalIconTheme.current): Painter {
var realResource = resource
if (theme == JBThemeStyle.DARK) {
if (!realResource.endsWith("_dark.svg")) {
val dark = realResource.replace(".svg", "_dark.svg")
if (Thread.currentThread().contextClassLoader.getResource(dark) != null) {
realResource = dark
}
}
} else {
if (realResource.endsWith("_dark.svg")) {
val light = realResource.replace("_dark.svg", ".svg")
if (Thread.currentThread().contextClassLoader.getResource(light) != null) {
realResource = light
}
}
}
return painterResource(realResource)
}
private fun Modifier.defaultSizeFor(painter: Painter) =
this.then(
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
DefaultIconSizeModifier
} else {
Modifier
}
)
private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
private val DefaultIconSizeModifier = Modifier.size(16.dp)
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBToolBar.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
@Composable
fun JBToolBar(
orientation: Orientation,
arrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(8.dp),
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalToolBarOrientation provides orientation,
) {
when (orientation) {
Orientation.Vertical -> JBToolBarColumn(
modifier.width(28.dp),
verticalArrangement = arrangement,
content = content
)
Orientation.Horizontal -> JBToolBarRow(
modifier.height(28.dp),
horizontalArrangement = arrangement,
content = content
)
}
}
}
@Composable
private fun JBToolBarColumn(
modifier: Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
content: @Composable () -> Unit,
) {
Column(
modifier,
verticalArrangement,
Alignment.CenterHorizontally,
) {
content()
}
}
@Composable
private fun JBToolBarRow(
modifier: Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp),
content: @Composable () -> Unit,
) {
Row(
modifier,
horizontalArrangement,
Alignment.CenterVertically,
) {
content()
}
}
val LocalToolBarOrientation = compositionLocalOf { Orientation.Horizontal }
@Composable
fun ToolBarSeparator(modifier: Modifier = Modifier, color: Color = JBTheme.toolBarColors.iconSplitBorder) {
val orientation = LocalToolBarOrientation.current
Spacer(
modifier = modifier.run {
when (orientation) {
Orientation.Vertical -> size(20.dp, 1.dp)
Orientation.Horizontal -> size(1.dp, 20.dp)
}
}.background(color)
)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBTree.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
import io.kanro.compose.jetbrains.JBThemeStyle
import io.kanro.compose.jetbrains.LocalIconTheme
import io.kanro.compose.jetbrains.icons.JBIcons
import io.kanro.compose.jetbrains.icons.jbicons.Actions
import io.kanro.compose.jetbrains.icons.jbicons.actions.ArrowExpand
import io.kanro.compose.jetbrains.icons.jbicons.actions.ArrowExpandDark
@Composable
fun JBTreeItem(
modifier: Modifier = Modifier,
selected: Boolean,
onClick: () -> Unit,
content: @Composable () -> Unit,
) {
JBTreeBasicItem(modifier, selected, onClick) {
Box(Modifier.padding(start = 16.dp)) {
content()
}
}
}
@Composable
fun JBTreeItem(
modifier: Modifier = Modifier,
selected: Boolean,
onClick: () -> Unit,
expanded: Boolean,
expanding: (Boolean) -> Unit,
content: @Composable () -> Unit,
children: @Composable () -> Unit,
) {
Column(modifier) {
JBTreeBasicItem(
modifier, selected, onClick = onClick,
onDoubleClick = {
expanding(!expanded)
onClick()
}
) {
Row {
val isDarkTheme = LocalIconTheme.current == JBThemeStyle.DARK
Icon(
imageVector = if (isDarkTheme) JBIcons.Actions.ArrowExpandDark else JBIcons.Actions.ArrowExpand,
modifier = Modifier.size(16.dp).clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
expanding(!expanded)
onClick()
}.graphicsLayer(rotationZ = if (expanded) 90f else 0f).align(Alignment.CenterVertically)
)
Box {
content()
}
}
}
if (expanded) {
CompositionLocalProvider(
LocalTreeLevel provides LocalTreeLevel.current + 1,
content = children
)
}
}
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
internal fun JBTreeBasicItem(
modifier: Modifier = Modifier,
selected: Boolean = false,
onClick: () -> Unit,
onDoubleClick: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit,
) {
val padding = 7 + (LocalTreeLevel.current - 1) * 18
Box(
Modifier.combinedClickable(
interactionSource,
indication = ListItemHoverIndication,
onDoubleClick = onDoubleClick,
onClick = onClick
).then(modifier)
.height(20.dp)
.run {
if (selected) {
background(color = JBTheme.selectionColors.active)
} else {
this
}
}.hoverable(interactionSource),
) {
SelectionScope(selected) {
Box(Modifier.padding(start = padding.dp)) {
content()
}
}
}
}
@Composable
fun JBTreeList(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Column(modifier) {
content()
}
}
val LocalTreeLevel: ProvidableCompositionLocal<Int> = compositionLocalOf {
1
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JPanel.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
@Composable
fun JPanel(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
Box(modifier.background(JBTheme.panelColors.bgDialog)) {
content()
}
}
@Composable
fun JPanelBorder(modifier: Modifier = Modifier) {
Spacer(modifier.background(JBTheme.panelColors.border))
}
fun Modifier.jBorder(all: Dp = 0.dp, color: Color): Modifier {
return jBorder(all, all, all, all, color)
}
fun Modifier.jBorder(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp,
color: Color,
): Modifier {
return jBorder(horizontal, horizontal, vertical, vertical, color)
}
fun Modifier.jBorder(
start: Dp = 0.dp,
end: Dp = 0.dp,
top: Dp = 0.dp,
bottom: Dp = 0.dp,
color: Color,
): Modifier {
return drawWithCache {
onDrawWithContent {
drawContent()
var rect = Rect(Offset.Zero, size)
if (start.roundToPx() > 0) {
drawRect(color, rect.topLeft, Size(start.toPx(), rect.height))
rect = Rect(rect.left + start.roundToPx(), rect.top, rect.right, rect.bottom)
}
if (end.roundToPx() > 0) {
drawRect(color, Offset(rect.right - end.toPx(), rect.top), Size(end.toPx(), rect.height))
rect = Rect(rect.left, rect.top, rect.right - end.roundToPx(), rect.bottom)
}
if (top.roundToPx() > 0) {
drawRect(color, rect.topLeft, Size(rect.width, top.toPx()))
rect = Rect(rect.left, rect.top + top.roundToPx(), rect.right, rect.bottom)
}
if (bottom.roundToPx() > 0) {
drawRect(color, Offset(rect.left, rect.bottom - bottom.toPx()), Size(rect.width, bottom.toPx()))
}
}
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ListView.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import io.kanro.compose.jetbrains.JBTheme
object ListItemHoverIndication : Indication {
private class HoverIndicationInstance(
private val isHover: State<Boolean>,
private val hoverColor: Color,
) : IndicationInstance {
override fun ContentDrawScope.drawIndication() {
if (isHover.value) {
drawRect(hoverColor, size = size)
}
drawContent()
}
}
@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val isHover = interactionSource.collectIsHoveredAsState()
val hoverColor = JBTheme.selectionColors.hover
return remember(JBTheme.selectionColors, interactionSource) {
HoverIndicationInstance(
isHover, hoverColor
)
}
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ProgressBar.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
@Composable
fun ProgressBar(
progress: Float,
modifier: Modifier = Modifier,
) {
val bgColor = JBTheme.progressColors.bg
val progressColor = JBTheme.progressColors.progress
Canvas(
modifier
.progressSemantics(progress)
.size(200.dp, 4.dp)
.focusable()
) {
val strokeWidth = size.height
val length = size.width
drawLine(
bgColor,
Offset(0f, strokeWidth / 2f),
Offset(length, strokeWidth / 2f),
strokeWidth,
cap = StrokeCap.Round
)
drawLine(
progressColor,
Offset(0f, strokeWidth / 2f),
Offset(length * progress, strokeWidth / 2f),
strokeWidth,
cap = StrokeCap.Round
)
}
}
@Composable
fun ProgressBar(
modifier: Modifier = Modifier,
) {
val transition = rememberInfiniteTransition()
val currentOffset by transition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = 1000
}
)
)
val progressColor = JBTheme.progressColors.progress
Canvas(
modifier
.progressSemantics()
.size(200.dp, 4.dp)
.focusable()
) {
val strokeWidth = size.height
val length = size.width
val offset = currentOffset * 80f
val brush = Brush.linearGradient(
listOf(
Color(0x00FFFFFF), Color(0x7FFFFFFF), Color(0x00FFFFFF)
),
start = Offset(offset, 0f),
end = Offset(offset + 80f, 0f),
tileMode = TileMode.Repeated
)
drawLine(
progressColor,
Offset(0f, strokeWidth / 2f),
Offset(length, strokeWidth / 2f),
strokeWidth,
cap = StrokeCap.Round
)
drawLine(
brush,
Offset(0f, strokeWidth / 2f),
Offset(length, strokeWidth / 2f),
strokeWidth,
cap = StrokeCap.Round
)
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Selection.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.selection.selectable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import io.kanro.compose.jetbrains.JBTheme
import io.kanro.compose.jetbrains.JBThemeStyle
import io.kanro.compose.jetbrains.LocalIconTheme
import io.kanro.compose.jetbrains.LocalSelectionScope
import io.kanro.compose.jetbrains.color.LocalTextColors
val emptySelectionScope = emptyArray<ProvidedValue<out Any>>()
val lightSelectionScope: @Composable () -> Array<ProvidedValue<out Any>> = {
arrayOf(
LocalIconTheme provides JBThemeStyle.DARK,
LocalTextColors provides JBTheme.textColors.copy(
infoInput = Color.White
),
LocalContentColor provides Color.White,
LocalContentAlpha provides 1.0f,
LocalContextMenuRepresentation provides JBContextMenuRepresentation(
JBTheme.panelColors.bgContent, JBTheme.panelColors.border
)
)
}
val darkSelectionScope: @Composable () -> Array<ProvidedValue<out Any>> = {
arrayOf(
LocalIconTheme provides JBThemeStyle.DARK,
LocalTextColors provides JBTheme.textColors.copy(
infoInput = Color.White
),
LocalContentColor provides Color.White,
LocalContentAlpha provides 1.0f,
LocalContextMenuRepresentation provides JBContextMenuRepresentation(
JBTheme.panelColors.bgContent, JBTheme.panelColors.border
)
)
}
@Composable
fun SelectionScope(selected: Boolean, block: @Composable () -> Unit) {
CompositionLocalProvider(* if (selected) LocalSelectionScope.current() else emptySelectionScope) {
block()
}
}
@Composable
fun SelectionRow(
selected: Boolean,
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
role: Role? = null,
onClick: () -> Unit,
content: @Composable RowScope.() -> Unit,
) {
SelectionScope(selected) {
val selectedColor = JBTheme.selectionColors.active
Row(
modifier = modifier.background(color = JBTheme.panelColors.bgContent).selectable(
selected = selected,
interactionSource = interactionSource,
indication = ListItemHoverIndication,
onClick = onClick,
role = role
).drawWithContent {
if (selected) {
drawRect(selectedColor, size = size)
}
drawContent()
},
horizontalArrangement = horizontalArrangement, verticalAlignment = verticalAlignment, content = content
)
}
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Tab.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.background
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.selection.selectable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.JBTheme
object TabIndication : Indication {
private class TabIndicationInstance(
private val isHover: State<Boolean>,
private val hoverColor: Color,
) : IndicationInstance {
override fun ContentDrawScope.drawIndication() {
if (isHover.value) {
drawRect(hoverColor, Offset.Zero, size)
}
drawContent()
}
}
@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
val isHover = interactionSource.collectIsHoveredAsState()
val hoverColor = JBTheme.tabColors.hover
return remember(JBTheme.tabColors, interactionSource) {
TabIndicationInstance(
isHover,
hoverColor,
)
}
}
}
@Composable
fun Tab(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable BoxScope.() -> Unit,
) {
val selectionColor = JBTheme.tabColors.selection
Box(
modifier
.hoverable(interactionSource)
.height(28.dp)
.run {
if (selected) {
background(JBTheme.tabColors.bgSelected)
} else {
this
}
}
.drawWithContent {
drawContent()
if (selected) {
val height = 3.dp.toPx()
drawRect(
selectionColor,
Offset(0f, size.height - height),
Size(size.width, height)
)
}
}.selectable(
selected = selected,
onClick = onClick,
enabled = enabled,
role = Role.Tab,
interactionSource = interactionSource,
indication = TabIndication
),
propagateMinConstraints = true,
contentAlignment = Alignment.Center,
content = content
)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Text.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import io.kanro.compose.jetbrains.color.LocalTextColors
import io.kanro.compose.jetbrains.color.TextColors
@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
textColors: TextColors = LocalTextColors.current,
) {
Text(
AnnotatedString(text),
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
emptyMap(),
onTextLayout,
style,
textColors,
)
}
@Composable
fun Text(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
textColors: TextColors = LocalTextColors.current,
) {
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.takeOrElse {
textColors.default
}.copy(alpha = LocalContentAlpha.current)
}
}
val mergedStyle = style.merge(
TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing,
),
)
BasicText(
text = text,
modifier = modifier,
style = mergedStyle,
onTextLayout = onTextLayout,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = 1,
inlineContent = inlineContent,
)
}
val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
@Composable
fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
val mergedStyle = LocalTextStyle.current.merge(value)
CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
}
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/control/TextField.kt
================================================
package io.kanro.compose.jetbrains.control
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import io.kanro.compose.jetbrains.JBTheme
import kotlin.math.max
import kotlin.math.roundToInt
@Stable
interface TextFieldStyle {
@Composable
fun textColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color>
@Composable
fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State<Color>
@Composable
fun placeholderColor(enabled: Boolean): State<Color>
@Composable
fun borderColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color>
@Composable
fun indicatorColor(isError: Boolean, interactionSource: InteractionSource): State<Color>
@Composable
fun cursorColor(isError: Boolean): State<Color>
}
private data class DefaultTextFieldStyle(
private val textColor: Color,
private val disabledTextColor: Color,
private val errorTextColor: Color,
private val backgroundColor: Color,
private val disabledBackgroundColor: Color,
private val placeholderColor: Color,
private val disabledPlaceholderColor: Color,
private val borderColor: Color,
private val disabledBorderColor: Color,
private val errorBorderColor: Color,
private val focusedBorderColor: Color,
private val indicatorColor: Color,
private val errorIndicatorColor: Color,
private val cursorColor: Color,
private val errorCursorColor: Color,
) : TextFieldStyle {
@Composable
override fun textColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color> {
return rememberUpdatedState(
when {
!enabled -> disabledTextColor
isError -> errorTextColor
else -> textColor
}
)
}
@Composable
override fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State<Color> {
return rememberUpdatedState(if (enabled) backgroundColor else disabledBackgroundColor)
}
@Composable
override fun placeholderColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
}
@Composable
override fun borderColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enabled -> disabledBorderColor
isError -> errorBorderColor
focused -> focusedBorderColor
else -> borderColor
}
return rememberUpdatedState(targetValue)
}
@Composable
override fun indicatorColor(
isError: Boolean,
interactionSource: InteractionSource,
): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
isError -> errorIndicatorColor
focused -> indicatorColor
else -> Color.Transparent
}
return rememberUpdatedState(targetValue)
}
@Composable
override fun cursorColor(isError: Boolean): State<Color> {
return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
}
}
@Composable
fun TextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = true,
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RectangleShape,
style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
) {
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
val textFieldValue = textFieldValueState.copy(text = value)
TextField(
enabled = enabled,
readOnly = readOnly,
value = textFieldValue,
onValueChange = {
textFieldValueState = it
if (value != it.text) {
onValueChange(it.text)
}
},
modifier = modifier,
singleLine = singleLine,
textStyle = textStyle,
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
isError = isError,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
maxLines = maxLines,
interactionSource = interactionSource,
shape = shape,
style = style
)
}
@Composable
fun TextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = true,
maxLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = RectangleShape,
style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
) {
val textColor = textStyle.color.takeOrElse {
style.textColor(enabled, isError, interactionSource).value
}.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
val transformedText = remember(value.annotatedString, visualTransformation) {
visualTransformation.filter(value.annotatedString)
}.text
val decoratedPlaceholder: @Composable ((Modifier) -> Unit)? =
if (placeholder != null && transformedText.isEmpty()) {
@Composable { _ ->
Box {
Decoration(
contentColor = style.placeholderColor(enabled).value,
content = placeholder,
typography = JBTheme.typography.default
)
}
}
} else null
TextFieldLayout(
modifier = modifier,
value = value,
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
textStyle = mergedTextStyle,
singleLine = singleLine,
maxLines = maxLines,
visualTransformation = visualTransformation,
interactionSource = interactionSource,
decoratedPlaceholder = decoratedPlaceholder,
leading = leadingIcon,
trailing = trailingIcon,
borderWidth = 1.dp,
borderColor = style.borderColor(enabled, isError, interactionSource).value,
indicatorWidth = 2.dp,
indicatorColor = style.indicatorColor(isError, interactionSource).value,
shape = shape,
backgroundColor = style.backgroundColor(enabled, interactionSource).value,
cursorColor = style.cursorColor(isError).value
)
}
@Composable
internal fun TextFieldLayout(
modifier: Modifier,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
enabled: Boolean,
readOnly: Boolean,
keyboardOptions: KeyboardOptions,
keyboardActions: KeyboardActions,
textStyle: TextStyle,
singleLine: Boolean,
maxLines: Int = Int.MAX_VALUE,
visualTransformation: VisualTransformation,
interactionSource: MutableInteractionSource,
decoratedPlaceholder: @Composable ((Modifier) -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
borderWidth: Dp,
borderColor: Color,
indicatorWidth: Dp,
indicatorColor: Color,
cursorColor: Color,
backgroundColor: Color,
shape: Shape,
) {
BasicTextField(
value = value,
modifier = Modifier
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
)
.background(backgroundColor, shape)
.then(modifier),
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = textStyle,
cursorBrush = SolidColor(cursorColor),
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = singleLine,
maxLines = maxLines,
decorationBox = @Composable { coreTextField ->
// places leading icon, input field, label, placeholder, trailing icon
IconsWithTextFieldLayout(
textField = coreTextField,
leading = leading,
trailing = trailing,
singleLine = singleLine,
placeholder = decoratedPlaceholder,
shape = shape,
borderWidth = borderWidth,
borderColor = borderColor,
indicatorWidth = indicatorWidth,
indicatorColor = indicatorColor,
)
}
)
}
private fun Modifier.textFieldIndicator(color: Color, shape: Shape, width: Dp, cornerRadius: Dp): Modifier {
if (color.alpha == 0f) return this
return drawBehind {
val controlOutline = shape.createOutline(size, layoutDirection, this)
val highlightOutline = RoundRect(controlOutline.bounds.inflate(width.toPx()), CornerRadius(cornerRadius.toPx()))
val highlightPath = Path().apply {
this.fillType = PathFillType.EvenOdd
addRoundRect(highlightOutline)
addOutline(controlOutline)
close()
}
drawPath(highlightPath, color)
}
}
@Composable
private fun IconsWithTextFieldLayout(
textField: @Composable () -> Unit,
placeholder: @Composable ((Modifier) -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
singleLine: Boolean,
shape: Shape,
borderWidth: Dp,
borderColor: Color,
indicatorWidth: Dp,
indicatorColor: Color,
) {
Layout(
content = {
Box(
Modifier
.layoutId("border")
.border(
width = borderWidth,
color = borderColor,
shape = shape
)
.textFieldIndicator(
width = indicatorWidth,
color = indicatorColor,
shape = shape,
cornerRadius = 4.dp
)
)
if (leading != null) {
Box(
modifier = Modifier.layoutId(LeadingId).then(IconDefaultSizeModifier)
.padding(start = HorizontalIconPadding),
contentAlignment = Alignment.Center
) {
leading()
}
}
if (trailing != null) {
Box(
modifier = Modifier.layoutId(TrailingId).then(IconDefaultSizeModifier)
.padding(end = HorizontalIconPadding),
contentAlignment = Alignment.Center
) {
trailing()
}
}
val padding = Modifier.padding(
start = HorizontalTextFieldPadding,
end = HorizontalTextFieldPadding
)
if (placeholder != null) {
placeholder(Modifier.layoutId(PlaceholderId).then(padding))
}
Box(
modifier = Modifier.layoutId(TextFieldId).then(padding),
propagateMinConstraints = true
) {
textField()
}
}
) { measurables, incomingConstraints ->
// used to calculate the constraints for measuring elements that will be placed in a row
var occupiedSpaceHorizontally = 0
val bottomPadding = VerticalTextFieldPadding.roundToPx()
// measure leading icon
val constraints =
incomingConstraints.copy(minWidth = 0, minHeight = 0)
val leadingPlaceable = measurables.find { it.layoutId == LeadingId }?.measure(constraints)
occupiedSpaceHorizontally += widthOrZero(
leadingPlaceable
)
// measure trailing icon
val trailingPlaceable = measurables.find { it.layoutId == TrailingId }
?.measure(constraints.offset(horizontal = -occupiedSpaceHorizontally))
occupiedSpaceHorizontally += widthOrZero(
trailingPlaceable
)
// measure label
constraints.offset(
horizontal = -occupiedSpaceHorizontally,
vertical = -bottomPadding
)
// measure text field
// on top we offset either by default padding or by label's half height if its too big
// minWidth must not be set to 0 due to how foundation TextField treats zero minWidth
val topPadding = bottomPadding
val textConstraints = incomingConstraints.offset(
horizontal = -occupiedSpaceHorizontally,
vertical = -bottomPadding - topPadding
).copy(minHeight = 0)
val textFieldPlaceable =
measurables.first { it.layoutId == TextFieldId }.measure(textConstraints)
// measure placeholder
val placeholderConstraints = textConstraints.copy(minWidth = 0)
val placeholderPlaceable =
measurables.find { it.layoutId == PlaceholderId }?.measure(placeholderConstraints)
val width =
calculateWidth(
leadingPlaceable,
trailingPlaceable,
textFieldPlaceable,
placeholderPlaceable,
incomingConstraints
)
val height =
calculateHeight(
leadingPlaceable,
trailingPlaceable,
textFieldPlaceable,
placeholderPlaceable,
incomingConstraints,
density
)
val borderPlaceable = measurables.first { it.layoutId == "border" }.measure(
Constraints(
minWidth = if (width != Constraints.Infinity) width else 0,
maxWidth = width,
minHeight = if (height != Constraints.Infinity) height else 0,
maxHeight = height
)
)
layout(width, height) {
place(
height,
width,
leadingPlaceable,
trailingPlaceable,
textFieldPlaceable,
placeholderPlaceable,
borderPlaceable,
singleLine,
density
)
}
}
}
private fun calculateWidth(
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
constraints: Constraints,
): Int {
val middleSection = maxOf(
textFieldPlaceable.width,
widthOrZero(placeholderPlaceable)
)
val wrappedWidth =
widthOrZero(leadingPlaceable) + middleSection + widthOrZero(
trailingPlaceable
)
return max(wrappedWidth, constraints.minWidth)
}
private fun calculateHeight(
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
constraints: Constraints,
density: Float,
): Int {
// middle section is defined as a height of the text field or placeholder ( whichever is
// taller) plus 16.dp or half height of the label if it is taller, given that the label
// is vertically centered to the top edge of the resulting text field's container
val inputFieldHeight = max(
textFieldPlaceable.height,
heightOrZero(placeholderPlaceable)
)
val topBottomPadding = VerticalTextFieldPadding.value * density
val middleSectionHeight = inputFieldHeight + topBottomPadding + topBottomPadding
return max(
constraints.minHeight,
maxOf(
heightOrZero(leadingPlaceable),
heightOrZero(trailingPlaceable),
middleSectionHeight.roundToInt()
)
)
}
private fun Placeable.PlacementScope.place(
height: Int,
width: Int,
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
borderPlaceable: Placeable,
singleLine: Boolean,
density: Float,
) {
val topBottomPadding = (VerticalTextFieldPadding.value * density).roundToInt()
// placed center vertically and to the start edge horizontally
leadingPlaceable?.placeRelative(
0,
Alignment.CenterVertically.align(leadingPlaceable.height, height)
)
// placed center vertically and to the end edge horizontally
trailingPlaceable?.placeRelative(
width - trailingPlaceable.width,
Alignment.CenterVertically.align(trailingPlaceable.height, height)
)
// placed center vertically and after the leading icon horizontally if single line text field
// placed to the top with padding for multi line text field
val textVerticalPosition = if (singleLine) {
Alignment.CenterVertically.align(textFieldPlaceable.height, height)
} else {
topBottomPadding
}
textFieldPlaceable.placeRelative(widthOrZero(leadingPlaceable), textVerticalPosition)
// placed similar to the input text above
placeholderPlaceable?.let {
val placeholderVerticalPosition = if (singleLine) {
Alignment.CenterVertically.align(it.height, height)
} else {
topBottomPadding
}
it.placeRelative(widthOrZero(leadingPlaceable), placeholderVerticalPosition)
}
// place border
borderPlaceable.place(IntOffset.Zero)
}
@Composable
internal fun Decoration(
contentColor: Color,
typography: TextStyle? = null,
contentAlpha: Float? = null,
content: @Composable () -> Unit,
) {
val colorAndEmphasis: @Composable () -> Unit = @Composable {
CompositionLocalProvider(LocalContentColor provides contentColor) {
if (contentAlpha != null) {
CompositionLocalProvider(
LocalContentAlpha provides contentAlpha,
content = content
)
} else {
CompositionLocalProvider(
LocalContentAlpha provides contentColor.alpha,
content = content
)
}
}
}
if (typography != null) ProvideTextStyle(typography, colorAndEmphasis) else colorAndEmphasis()
}
object TextFieldDefaults {
val MinHeight = 24.dp
val MinWidth = 64.dp
@Composable
fun textFieldStyle(
textColor: Color = JBTheme.textColors.default,
disabledTextColor: Color = JBTheme.textColors.disabled,
errorTextColor: Color = JBTheme.textColors.error,
backgroundColor: Color = JBTheme.fieldColors.bg,
disabledBackgroundColor: Color = JBTheme.fieldColors.bgDisabled,
placeholderColor: Color = JBTheme.textColors.infoInput,
disabledPlaceholderColor: Color = JBTheme.textColors.infoInput,
borderColor: Color = JBTheme.fieldColors.border,
disabledBorderColor: Color = JBTheme.fieldColors.borderDisabled,
errorBorderColor: Color = JBTheme.fieldColors.borderError,
focusedBorderColor: Color = JBTheme.fieldColors.borderFocused,
indicatorColor: Color = JBTheme.focusColors.default,
errorIndicatorColor: Color = JBTheme.focusColors.error,
cursorColor: Color = JBTheme.textColors.default,
errorCursorColor: Color = JBTheme.textColors.error,
): TextFieldStyle = DefaultTextFieldStyle(
textColor,
disabledTextColor,
errorTextColor,
backgroundColor,
disabledBackgroundColor,
placeholderColor,
disabledPlaceholderColor,
borderColor,
disabledBorderColor,
errorBorderColor,
focusedBorderColor,
indicatorColor,
errorIndicatorColor,
cursorColor,
errorCursorColor
)
}
internal fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
internal fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
internal val HorizontalTextFieldPadding = 6.dp
internal val VerticalTextFieldPadding = 3.dp
internal val HorizontalIconPadding = 6.dp
internal val IconDefaultSizeModifier = Modifier.defaultMinSize(16.dp, 16.dp)
private const val PlaceholderId = "Placeholder"
private const val TextFieldId = "TextField"
private const val LeadingId = "Leading"
private const val TrailingId = "Trailing"
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/__JBIcons.kt
================================================
package io.kanro.compose.jetbrains.icons
internal object JBIcons
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__Actions.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons
import io.kanro.compose.jetbrains.icons.JBIcons
internal object ActionsGroup
internal val JBIcons.Actions: ActionsGroup
get() = ActionsGroup
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__General.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons
import io.kanro.compose.jetbrains.icons.JBIcons
internal object GeneralGroup
internal val JBIcons.General: GeneralGroup
get() = GeneralGroup
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpand.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.actions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
internal val ActionsGroup.ArrowExpand: ImageVector
get() {
if (_arrowExpand != null) {
return _arrowExpand!!
}
_arrowExpand = Builder(
name = "ArrowExpand", defaultWidth = 16.0.dp,
defaultHeight =
16.0.dp,
viewportWidth = 16.0f, viewportHeight = 16.0f
).apply {
path(
fill = SolidColor(Color(0x00000000)), stroke = SolidColor(Color(0xFF6E6E6E)),
strokeLineWidth = 2.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
strokeLineMiter = 4.0f, pathFillType = NonZero
) {
moveTo(6.0f, 13.0f)
lineTo(11.0f, 8.0f)
lineTo(6.0f, 3.0f)
}
}
.build()
return _arrowExpand!!
}
private var _arrowExpand: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpandDark.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.actions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
internal val ActionsGroup.ArrowExpandDark: ImageVector
get() {
if (_arrowExpandDark != null) {
return _arrowExpandDark!!
}
_arrowExpandDark = Builder(
name = "ArrowexpandDark", defaultWidth = 16.0.dp,
defaultHeight =
16.0.dp,
viewportWidth = 16.0f, viewportHeight = 16.0f
).apply {
path(
fill = SolidColor(Color(0x00000000)), stroke = SolidColor(Color(0xFFAFB1B3)),
strokeLineWidth = 2.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
strokeLineMiter = 4.0f, pathFillType = NonZero
) {
moveTo(6.0f, 13.0f)
lineTo(11.0f, 8.0f)
lineTo(6.0f, 3.0f)
}
}
.build()
return _arrowExpandDark!!
}
private var _arrowExpandDark: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Checkmark.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.actions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
internal val ActionsGroup.Checkmark: ImageVector
get() {
if (_checkmark != null) {
return _checkmark!!
}
_checkmark = Builder(
name = "Checkmark", defaultWidth = 14.0.dp, defaultHeight = 14.0.dp,
viewportWidth = 14.0f, viewportHeight = 14.0f
).apply {
path(
fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = EvenOdd
) {
moveTo(5.625f, 8.4267f)
lineTo(9.5566f, 2.9336f)
curveTo(9.5566f, 2.9336f, 10.1737f, 2.3242f, 10.8612f, 2.8242f)
curveTo(11.4433f, 3.3867f, 10.998f, 4.0938f, 10.998f, 4.0938f)
lineTo(6.3183f, 10.6445f)
curveTo(6.3183f, 10.6445f, 5.9941f, 11.0f, 5.5839f, 11.0f)
curveTo(5.1737f, 11.0f, 4.873f, 10.6445f, 4.873f, 10.6445f)
lineTo(2.9394f, 7.7461f)
curveTo(2.9394f, 7.7461f, 2.5683f, 6.9805f, 3.2558f, 6.4609f)
curveTo(4.0605f, 6.0781f, 4.5605f, 6.8394f, 4.5605f, 6.8394f)
lineTo(5.625f, 8.4267f)
close()
}
}
.build()
return _checkmark!!
}
private var _checkmark: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/CheckmarkIndeterminate.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.actions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
internal val ActionsGroup.CheckmarkIndeterminate: ImageVector
get() {
if (_checkmarkIndeterminate != null) {
return _checkmarkIndeterminate!!
}
_checkmarkIndeterminate = Builder(
name = "CheckmarkIndeterminate", defaultWidth = 14.0.dp,
defaultHeight = 14.0.dp, viewportWidth = 14.0f, viewportHeight = 14.0f
).apply {
path(
fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = NonZero
) {
moveTo(3.7402f, 5.73f)
lineTo(10.1402f, 5.73f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 11.1402f, 6.73f)
lineTo(11.1402f, 7.23f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 10.1402f, 8.23f)
lineTo(3.7402f, 8.23f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 2.7402f, 7.23f)
lineTo(2.7402f, 6.73f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 3.7402f, 5.73f)
close()
}
}
.build()
return _checkmarkIndeterminate!!
}
private var _checkmarkIndeterminate: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Close.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.actions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
internal val ActionsGroup.Close: ImageVector
get() {
if (_close != null) {
return _close!!
}
_close = Builder(
name = "Close", defaultWidth = 16.0.dp, defaultHeight = 16.0.dp,
viewportWidth = 16.0f, viewportHeight = 16.0f
).apply {
path(
fill = SolidColor(Color(0xFF7F8B91)), stroke = null, fillAlpha = 0.5f,
strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
strokeLineMiter = 4.0f, pathFillType = EvenOdd
) {
moveTo(7.9949f, 8.7051f)
lineTo(4.8541f, 11.8541f)
lineTo(4.147f, 11.147f)
lineTo(7.2949f, 8.0051f)
lineTo(4.147f, 4.8571f)
lineTo(4.8541f, 4.15f)
lineTo(8.002f, 7.298f)
lineTo(11.144f, 4.15f)
lineTo(11.8511f, 4.8571f)
lineTo(8.702f, 7.998f)
lineTo(11.8511f, 11.147f)
lineTo(11.144f, 11.8541f)
lineTo(7.9949f, 8.7051f)
close()
}
}
.build()
return _close!!
}
private var _close: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangle.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.general
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.GeneralGroup
internal val GeneralGroup.ButtonDropTriangle: ImageVector
get() {
if (_buttonDropTriangle != null) {
return _buttonDropTriangle!!
}
_buttonDropTriangle = Builder(
name = "ButtonDropTriangle", defaultWidth = 8.0.dp,
defaultHeight = 4.0.dp, viewportWidth = 8.0f, viewportHeight = 4.0f
).apply {
path(
fill = SolidColor(Color(0xFF6E6E6E)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = EvenOdd
) {
moveTo(4.0f, 4.0f)
lineToRelative(4.0f, -4.0f)
lineToRelative(-8.0f, -0.0f)
close()
}
}
.build()
return _buttonDropTriangle!!
}
private var _buttonDropTriangle: ImageVector? = null
================================================
FILE: classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangleDark.kt
================================================
package io.kanro.compose.jetbrains.icons.jbicons.general
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.icons.jbicons.GeneralGroup
internal val GeneralGroup.ButtonDropTriangleDark: ImageVector
get() {
if (_buttonDropTriangleDark != null) {
return _buttonDropTriangleDark!!
}
_buttonDropTriangleDark = Builder(
name = "ButtonDropTriangleDark", defaultWidth = 8.0.dp,
defaultHeight = 4.0.dp, viewportWidth = 8.0f, viewportHeight = 4.0f
).apply {
path(
fill = SolidColor(Color(0xFFAFB1B3)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = EvenOdd
) {
moveTo(4.0f, 4.0f)
lineToRelative(4.0f, -4.0f)
lineToRelative(-8.0f, -0.0f)
close()
}
}
.build()
return _buttonDropTriangleDark!!
}
private var _buttonDropTriangleDark: ImageVector? = null
================================================
FILE: expui/build.gradle.kts
================================================
plugins {
kotlin("jvm")
id("com.netflix.nebula.maven-publish")
id("com.netflix.nebula.source-jar")
id("com.bybutter.sisyphus.project")
id("org.jetbrains.compose")
`java-library`
}
description = "JetBrains ExpUI Kit for Compose Desktop"
dependencies {
implementation(kotlin("stdlib"))
implementation(compose.desktop.common) {
exclude("org.jetbrains.compose.material")
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() {
kotlinOptions.jvmTarget = "17"
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/DesktopPlatform.kt
================================================
package io.kanro.compose.jetbrains.expui
enum class DesktopPlatform {
Linux,
Windows,
MacOS,
Unknown;
companion object {
val Current: DesktopPlatform by lazy {
val name = System.getProperty("os.name")
when {
name?.startsWith("Linux") == true -> Linux
name?.startsWith("Win") == true -> Windows
name == "Mac OS X" -> MacOS
else -> Unknown
}
}
}
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ActionButton.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.foundation.Indication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.expui.style.AreaColors
import io.kanro.compose.jetbrains.expui.style.AreaProvider
import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
import io.kanro.compose.jetbrains.expui.style.HoverAreaProvider
import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalHoverAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalPressedAreaColors
import io.kanro.compose.jetbrains.expui.style.PressedAreaProvider
import io.kanro.compose.jetbrains.expui.style.areaBackground
import io.kanro.compose.jetbrains.expui.theme.LightTheme
class ActionButtonColors(
override val normalAreaColors: AreaColors,
override val hoverAreaColors: AreaColors,
override val pressedAreaColors: AreaColors,
override val disabledAreaColors: AreaColors,
) : AreaProvider, HoverAreaProvider, PressedAreaProvider, DisabledAreaProvider {
@Composable
fun provideArea(enabled: Boolean, content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalAreaColors provides if (enabled) normalAreaColors else disabledAreaColors,
LocalNormalAreaColors provides normalAreaColors,
LocalDisabledAreaColors provides disabledAreaColors,
LocalHoverAreaColors provides hoverAreaColors,
LocalPressedAreaColors provides pressedAreaColors,
content = content
)
}
}
val LocalActionButtonColors = compositionLocalOf {
LightTheme.ActionButtonColors
}
@Composable
fun ActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = RoundedCornerShape(6.dp),
indication: Indication? = HoverOrPressedIndication(shape),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ActionButtonColors = LocalActionButtonColors.current,
content: @Composable BoxScope.() -> Unit,
) {
colors.provideArea(enabled) {
Box(
modifier.areaBackground(shape = shape).clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClick = onClick,
role = Role.Button
),
propagateMinConstraints = true
) {
content()
}
}
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Button.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.expui.style.AreaColors
import io.kanro.compose.jetbrains.expui.style.AreaProvider
import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
import io.kanro.compose.jetbrains.expui.style.FocusAreaProvider
import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalFocusAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
import io.kanro.compose.jetbrains.expui.theme.LightTheme
class ButtonColors(
override val normalAreaColors: AreaColors,
override val focusAreaColors: AreaColors,
override val disabledAreaColors: AreaColors,
) : AreaProvider, FocusAreaProvider, DisabledAreaProvider {
@Composable
fun provideArea(enabled: Boolean, focused: Boolean, content: @Composable () -> Unit) {
val currentAreaColor = when {
!enabled -> disabledAreaColors
focused -> focusAreaColors
else -> normalAreaColors
}
CompositionLocalProvider(
LocalAreaColors provides currentAreaColor,
LocalNormalAreaColors provides normalAreaColors,
LocalFocusAreaColors provides focusAreaColors,
LocalDisabledAreaColors provides disabledAreaColors,
content = content
)
}
}
val LocalPrimaryButtonColors = compositionLocalOf {
LightTheme.PrimaryButtonColors
}
val LocalOutlineButtonColors = compositionLocalOf {
LightTheme.OutlineButtonColors
}
@Composable
fun OutlineButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ButtonColors = LocalOutlineButtonColors.current,
content: @Composable RowScope.() -> Unit,
) {
ButtonImpl(onClick, modifier, enabled, interactionSource, colors, content)
}
@Composable
fun PrimaryButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ButtonColors = LocalPrimaryButtonColors.current,
content: @Composable RowScope.() -> Unit,
) {
ButtonImpl(onClick, modifier, enabled, interactionSource, colors, content)
}
@Composable
private fun ButtonImpl(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ButtonColors,
content: @Composable RowScope.() -> Unit,
) {
val isFocused = remember { mutableStateOf(false) }
colors.provideArea(enabled, isFocused.value) {
val areaColors = LocalAreaColors.current
Box(
Modifier.defaultMinSize(72.dp, 24.dp).drawWithCache {
onDrawBehind {
if (isFocused.value) {
drawRoundRect(
areaColors.focusColor,
size = Size(size.width + 4.dp.toPx(), size.height + 4.dp.toPx()),
topLeft = Offset(-2.dp.toPx(), -2.dp.toPx()),
cornerRadius = CornerRadius(5.dp.toPx())
)
}
drawRoundRect(areaColors.startBorderColor, cornerRadius = CornerRadius(3.dp.toPx()))
drawRoundRect(
areaColors.startBackground,
size = Size(size.width - 2.dp.toPx(), size.height - 2.dp.toPx()),
topLeft = Offset(1.dp.toPx(), 1.dp.toPx()),
cornerRadius = CornerRadius(2.dp.toPx())
)
}
}.onFocusEvent {
isFocused.value = it.isFocused
}.clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = onClick,
role = Role.Button
).then(modifier),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier.padding(14.dp, 3.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
}
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/CheckBox.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.expui.style.AreaColors
import io.kanro.compose.jetbrains.expui.style.AreaProvider
import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
import io.kanro.compose.jetbrains.expui.style.FocusAreaProvider
import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalFocusAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalSelectionAreaColors
import io.kanro.compose.jetbrains.expui.style.SelectionAreaProvider
import io.kanro.compose.jetbrains.expui.theme.LightTheme
class CheckBoxColors(
override val normalAreaColors: AreaColors,
override val selectionAreaColors: AreaColors,
override val focusAreaColors: AreaColors,
override val disabledAreaColors: AreaColors,
) : AreaProvider, DisabledAreaProvider, FocusAreaProvider, SelectionAreaProvider {
@Composable
fun provideArea(enabled: Boolean, focused: Boolean, selected: Boolean, content: @Composable () -> Unit) {
val currentColors = when {
!enabled -> disabledAreaColors
focused -> focusAreaColors
selected -> selectionAreaColors
else -> normalAreaColors
}
CompositionLocalProvider(
LocalAreaColors provides currentColors,
LocalNormalAreaColors provides normalAreaColors,
LocalSelectionAreaColors provides selectionAreaColors,
LocalFocusAreaColors provides focusAreaColors,
LocalDisabledAreaColors provides disabledAreaColors,
content = content
)
}
}
val LocalCheckBoxColors = compositionLocalOf<CheckBoxColors> {
LightTheme.CheckBoxColors
}
@Composable
fun Checkbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckBoxColors = LocalCheckBoxColors.current,
) {
TriStateCheckbox(
state = ToggleableState(checked),
onClick = { onCheckedChange(!checked) },
interactionSource = interactionSource,
enabled = enabled,
modifier = modifier,
colors = colors
)
}
@Composable
fun Checkbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckBoxColors = LocalCheckBoxColors.current,
content: @Composable () -> Unit,
) {
TriStateCheckbox(
state = ToggleableState(checked),
onClick = { onCheckedChange(!checked) },
interactionSource = interactionSource,
enabled = enabled,
modifier = modifier,
colors = colors,
content = content
)
}
@Composable
fun TriStateCheckbox(
state: ToggleableState,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckBoxColors = LocalCheckBoxColors.current,
) {
val isFocused = remember { mutableStateOf(false) }
colors.provideArea(enabled, isFocused.value, state != ToggleableState.Off) {
CheckboxImpl(
isFocused = isFocused.value, value = state,
modifier = Modifier.onFocusEvent {
isFocused.value = it.isFocused
}.triStateToggleable(
state = state,
onClick = onClick,
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = null
)
)
}
}
@Composable
fun TriStateCheckbox(
state: ToggleableState,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckBoxColors = LocalCheckBoxColors.current,
content: @Composable () -> Unit,
) {
val isFocused = remember { mutableStateOf(false) }
colors.provideArea(enabled, isFocused.value, state != ToggleableState.Off) {
Row(
modifier.onFocusEvent {
isFocused.value = it.isFocused
}.triStateToggleable(
state = state,
onClick = onClick,
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = null
),
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
CheckboxImpl(isFocused = isFocused.value, value = state)
content()
}
}
}
private fun Checkmark() = ImageVector.Builder(
name = "Checkmark", defaultWidth = 14.0.dp, defaultHeight = 14.0.dp, viewportWidth = 14.0f, viewportHeight = 14.0f
).apply {
path(
fill = SolidColor(Color(0xFFffffff)),
stroke = null,
strokeLineWidth = 0.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 4.0f,
pathFillType = PathFillType.EvenOdd
) {
moveTo(5.625f, 8.4267f)
lineTo(9.5566f, 2.9336f)
curveTo(9.5566f, 2.9336f, 10.1737f, 2.3242f, 10.8612f, 2.8242f)
curveTo(11.4433f, 3.3867f, 10.998f, 4.0938f, 10.998f, 4.0938f)
lineTo(6.3183f, 10.6445f)
curveTo(6.3183f, 10.6445f, 5.9941f, 11.0f, 5.5839f, 11.0f)
curveTo(5.1737f, 11.0f, 4.873f, 10.6445f, 4.873f, 10.6445f)
lineTo(2.9394f, 7.7461f)
curveTo(2.9394f, 7.7461f, 2.5683f, 6.9805f, 3.2558f, 6.4609f)
curveTo(4.0605f, 6.0781f, 4.5605f, 6.8394f, 4.5605f, 6.8394f)
lineTo(5.625f, 8.4267f)
close()
}
}.build()
private fun CheckmarkIndeterminate() = ImageVector.Builder(
name = "CheckmarkIndeterminate",
defaultWidth = 14.0.dp,
defaultHeight = 14.0.dp,
viewportWidth = 14.0f,
viewportHeight = 14.0f
).apply {
path(
fill = SolidColor(Color(0xFFffffff)),
stroke = null,
strokeLineWidth = 0.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 4.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(3.7402f, 5.73f)
lineTo(10.1402f, 5.73f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 11.1402f, 6.73f)
lineTo(11.1402f, 7.23f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 10.1402f, 8.23f)
lineTo(3.7402f, 8.23f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 2.7402f, 7.23f)
lineTo(2.7402f, 6.73f)
arcTo(1.0f, 1.0f, 0.0f, false, true, 3.7402f, 5.73f)
close()
}
}.build()
@Composable
private fun CheckboxImpl(
isFocused: Boolean,
value: ToggleableState,
modifier: Modifier = Modifier,
) {
val icon = when (value) {
ToggleableState.On -> rememberVectorPainter(Checkmark())
ToggleableState.Indeterminate -> rememberVectorPainter(CheckmarkIndeterminate())
else -> null
}
val colors = LocalAreaColors.current
Canvas(modifier.wrapContentSize(Alignment.Center).requiredSize(14.dp)) {
if (isFocused) {
drawRoundRect(
colors.focusColor,
size = Size(18.dp.toPx(), 18.dp.toPx()),
topLeft = Offset(-2.dp.toPx(), -2.dp.toPx()),
cornerRadius = CornerRadius(4.dp.toPx())
)
}
drawRoundRect(colors.startBorderColor, cornerRadius = CornerRadius(2.dp.toPx()))
drawRoundRect(
colors.startBackground,
size = Size(12.dp.toPx(), 12.dp.toPx()),
topLeft = Offset(1.dp.toPx(), 1.dp.toPx()),
cornerRadius = CornerRadius(1.dp.toPx())
)
if (icon != null) {
with(icon) {
14.dp.toPx()
draw(Size(14.dp.toPx(), 14.dp.toPx()), colorFilter = ColorFilter.tint(colors.foreground))
}
}
}
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ComboBox.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.kanro.compose.jetbrains.expui.style.AreaColors
import io.kanro.compose.jetbrains.expui.style.AreaProvider
import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
import io.kanro.compose.jetbrains.expui.style.FocusAreaProvider
import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalFocusAreaColors
import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
class ComboBoxColors(
override val normalAreaColors: AreaColors,
override val focusAreaColors: AreaColors,
override val disabledAreaColors: AreaColors,
val dropdownMenuColors: DropdownMenuColors,
) : AreaProvider, FocusAreaProvider, DisabledAreaProvider {
@Composable
fun provideArea(enabled: Boolean, focused: Boolean, content: @Composable () -> Unit) {
val currentAreaColor = when {
!enabled -> disabledAreaColors
focused -> focusAreaColors
else -> normalAreaColors
}
CompositionLocalProvider(
LocalAreaColors provides currentAreaColor,
LocalNormalAreaColors provides normalAreaColors,
LocalFocusAreaColors provides focusAreaColors,
LocalDisabledAreaColors provides disabledAreaColors,
LocalDropdownMenuColors provides dropdownMenuColors,
content = content
)
}
}
val LocalComboBoxColors = compositionLocalOf<ComboBoxColors> {
error("No ComboBoxColors provided")
}
@Composable
fun <T> ComboBox(
items: List<T>,
value: T,
onValueChange: ((T) -> Unit)? = null,
modifier: Modifier = Modifier,
menuModifier: Modifier = Modifier,
enabled: Boolean = true,
valueRender: @Composable (T) -> Unit = { Label("$it") },
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ComboBoxColors = LocalComboBoxColors.current,
) {
val isFocused = remember { mutableStateOf(false) }
var menuOpened by remember { mutableStateOf(false) }
colors.provideArea(enabled, isFocused.value) {
val areaColors = LocalAreaColors.current
Box {
Box(
Modifier.defaultMinSize(72.dp, 24.dp).drawWithCache {
onDrawBehind {
if (isFocused.value) {
drawRoundRect(
areaColors.focusColor,
size = Size(size.width + 4.dp.toPx(), size.height + 4.dp.toPx()),
topLeft = Offset(-2.dp.toPx(), -2.dp.toPx()),
cornerRadius = CornerRadius(5.dp.toPx())
)
}
drawRoundRect(areaColors.startBorderColor, cornerRadius = CornerRadius(3.dp.toPx()))
drawRoundRect(
areaColors.startBackground,
size = Size(size.width - 2.dp.toPx(), size.height - 2.dp.toPx()),
topLeft = Offset(1.dp.toPx(), 1.dp.toPx()),
cornerRadius = CornerRadius(2.dp.toPx())
)
}
}.onFocusEvent {
isFocused.value = it.isFocused
}.clickable(
interactionSource = interactionSource, indication = null, enabled = enabled, onClick = {
menuOpened = true
}, role = Role.Button
).padding(6.dp, 3.dp).then(modifier),
contentAlignment = Alignment.CenterStart
) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
valueRender(value)
}
Icon("icons/buttonDropTriangle.svg", modifier = Modifier.align(Alignment.CenterEnd))
}
DropdownMenu(menuOpened, { menuOpened = false }, modifier = menuModifier) {
items.forEach { item ->
val focusRequester = remember { FocusRequester() }
DropdownMenuItem(
onClick = {
if (value != item) {
onValueChange?.invoke(item)
}
menuOpened = false
},
Modifier.focusRequester(focusRequester).onFocusEvent {
if (it.isFocused && value != item) {
onValueChange?.invoke(item)
}
}
) {
valueRender(item)
}
LaunchedEffect(menuOpened) {
if (menuOpened && value == item) {
focusRequester.requestFocus()
}
}
}
}
}
}
}
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ContextCompositionLocals.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.runtime.compositionLocalOf
val LocalContentActivated = compositionLocalOf { true }
================================================
FILE: expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ContextMenu.kt
================================================
package io.kanro.compose.jetbrains.expui.control
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ContextMenuRepresentation
import androidx.compose.foundation.ContextMenuState
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.co
gitextract_99aa8iq5/ ├── .github/ │ └── workflows/ │ ├── build.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── classic/ │ ├── README.md │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── kotlin/ │ └── io/ │ └── kanro/ │ └── compose/ │ └── jetbrains/ │ ├── JBTheme.kt │ ├── JBTypography.kt │ ├── color/ │ │ ├── ButtonColors.kt │ │ ├── CheckBoxColors.kt │ │ ├── FieldColors.kt │ │ ├── FocusColors.kt │ │ ├── IconColors.kt │ │ ├── PanelColors.kt │ │ ├── ProgressColors.kt │ │ ├── ScrollColors.kt │ │ ├── SelectionColors.kt │ │ ├── TabColors.kt │ │ ├── TableColors.kt │ │ ├── TextColors.kt │ │ ├── ToggleColors.kt │ │ └── ToolBarColors.kt │ ├── control/ │ │ ├── ActionButton.kt │ │ ├── Button.kt │ │ ├── CheckBox.kt │ │ ├── ComboBox.kt │ │ ├── ContentAlpha.kt │ │ ├── ContentColor.kt │ │ ├── ContextMenu.kt │ │ ├── DropdownMenu.kt │ │ ├── EmbeddedTextField.kt │ │ ├── Icon.kt │ │ ├── JBToolBar.kt │ │ ├── JBTree.kt │ │ ├── JPanel.kt │ │ ├── ListView.kt │ │ ├── ProgressBar.kt │ │ ├── Selection.kt │ │ ├── Tab.kt │ │ ├── Text.kt │ │ └── TextField.kt │ └── icons/ │ ├── __JBIcons.kt │ └── jbicons/ │ ├── __Actions.kt │ ├── __General.kt │ ├── actions/ │ │ ├── ArrowExpand.kt │ │ ├── ArrowExpandDark.kt │ │ ├── Checkmark.kt │ │ ├── CheckmarkIndeterminate.kt │ │ └── Close.kt │ └── general/ │ ├── ButtonDropTriangle.kt │ └── ButtonDropTriangleDark.kt ├── expui/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── kotlin/ │ └── io/ │ └── kanro/ │ └── compose/ │ └── jetbrains/ │ └── expui/ │ ├── DesktopPlatform.kt │ ├── control/ │ │ ├── ActionButton.kt │ │ ├── Button.kt │ │ ├── CheckBox.kt │ │ ├── ComboBox.kt │ │ ├── ContextCompositionLocals.kt │ │ ├── ContextMenu.kt │ │ ├── DropdownMenu.kt │ │ ├── Icon.kt │ │ ├── Indication.kt │ │ ├── Label.kt │ │ ├── Link.kt │ │ ├── Painter.kt │ │ ├── PointerInput.kt │ │ ├── ProgressBar.kt │ │ ├── RadioButton.kt │ │ ├── SegmentedButton.kt │ │ ├── Tab.kt │ │ ├── TextArea.kt │ │ ├── TextField.kt │ │ ├── ToolBar.kt │ │ └── Tooltip.kt │ ├── style/ │ │ ├── AreaColors.kt │ │ ├── AreaProvider.kt │ │ ├── Border.kt │ │ └── TextStyle.kt │ ├── theme/ │ │ ├── DarkTheme.kt │ │ ├── Fonts.kt │ │ ├── LightTheme.kt │ │ └── Theme.kt │ ├── util/ │ │ ├── CustomWindowDecorationAccessing.kt │ │ └── UnsafeAccessing.kt │ └── window/ │ ├── JBWindow.Linux.kt │ ├── JBWindow.MacOS.kt │ ├── JBWindow.Windows.kt │ ├── JBWindow.kt │ ├── MainToolBar.Basic.kt │ ├── MainToolBar.Linux.kt │ ├── MainToolBar.MacOS.kt │ ├── MainToolBar.Windows.kt │ └── MainToolBar.kt ├── expui-gallery/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── kotlin/ │ └── Main.kt ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts
Condensed preview — 104 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (461K chars).
[
{
"path": ".github/workflows/build.yml",
"chars": 3772,
"preview": "name: Build\non:\n # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g"
},
{
"path": ".github/workflows/release.yml",
"chars": 3271,
"preview": "# GitHub Actions Workflow created for handling the release process based on the draft release prepared\n# with the Build "
},
{
"path": ".gitignore",
"chars": 312,
"preview": ".gradle\n/build/\n!foundation/gradle/wrapper/gradle-wrapper.jar\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.proje"
},
{
"path": "CHANGELOG.md",
"chars": 654,
"preview": "# Changelog\n\n## [Unreleased]\n\n## [2.2.0]\n- Upgrade to compose 1.5.2\n\n## [2.1.0]\n- Provide BasicMainToolBar #18 \n- Disabl"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2020 ButterCam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 4752,
"preview": "# We are planning to migrate to [JetBrains/jewel](https://github.com/JetBrains/jewel)!\n\nWe are discussing with JetBrains"
},
{
"path": "build.gradle.kts",
"chars": 1355,
"preview": "plugins {\n id(\"com.bybutter.sisyphus.project\") version \"2.1.0\" apply false\n id(\"com.netflix.nebula.contacts\") vers"
},
{
"path": "classic/README.md",
"chars": 1513,
"preview": "# JetBrains UI Kit for Compose Desktop\n\nJetBrains style controls and UI for [Compose Desktop](https://www.jetbrains.com/"
},
{
"path": "classic/build.gradle.kts",
"chars": 684,
"preview": "plugins {\n kotlin(\"jvm\")\n `java-library`\n id(\"com.netflix.nebula.maven-publish\")\n id(\"com.netflix.nebula.sou"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/JBTheme.kt",
"chars": 9679,
"preview": "package io.kanro.compose.jetbrains\n\nimport androidx.compose.foundation.LocalScrollbarStyle\nimport androidx.compose.runti"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/JBTypography.kt",
"chars": 3527,
"preview": "package io.kanro.compose.jetbrains\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.ui.text.T"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ButtonColors.kt",
"chars": 6185,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.foundation.interaction.InteractionSource\nimport androi"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/CheckBoxColors.kt",
"chars": 1962,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FieldColors.kt",
"chars": 3506,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.animation.animateColorAsState\nimport androidx.compose."
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FocusColors.kt",
"chars": 1262,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/IconColors.kt",
"chars": 926,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/PanelColors.kt",
"chars": 1067,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ProgressColors.kt",
"chars": 905,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ScrollColors.kt",
"chars": 1125,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.foundation.ScrollbarStyle\nimport androidx.compose.foun"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/SelectionColors.kt",
"chars": 1696,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TabColors.kt",
"chars": 1660,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TableColors.kt",
"chars": 440,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutab"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TextColors.kt",
"chars": 1851,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.run"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToggleColors.kt",
"chars": 387,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutab"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToolBarColors.kt",
"chars": 1967,
"preview": "package io.kanro.compose.jetbrains.color\n\nimport androidx.compose.foundation.interaction.InteractionSource\nimport androi"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ActionButton.kt",
"chars": 5273,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foun"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Button.kt",
"chars": 9681,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/CheckBox.kt",
"chars": 4109,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ComboBox.kt",
"chars": 4883,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentAlpha.kt",
"chars": 146,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.runtime.compositionLocalOf\n\nval LocalContentAlpha = "
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentColor.kt",
"chars": 534,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.c"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContextMenu.kt",
"chars": 5633,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.ContextMenuData\nimport androidx.compose.f"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/DropdownMenu.kt",
"chars": 7170,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/EmbeddedTextField.kt",
"chars": 4438,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.interaction.InteractionSource\nimport andr"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Icon.kt",
"chars": 4262,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBToolBar.kt",
"chars": 2694,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBTree.kt",
"chars": 4431,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JPanel.kt",
"chars": 2412,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ListView.kt",
"chars": 1363,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ProgressBar.kt",
"chars": 2992,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.animation.core.animateFloat\nimport androidx.compose."
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Selection.kt",
"chars": 3418,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Tab.kt",
"chars": 3377,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Text.kt",
"chars": 4185,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.fo"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/control/TextField.kt",
"chars": 23794,
"preview": "package io.kanro.compose.jetbrains.control\n\nimport androidx.compose.foundation.background\nimport androidx.compose.founda"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/__JBIcons.kt",
"chars": 66,
"preview": "package io.kanro.compose.jetbrains.icons\n\ninternal object JBIcons\n"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__Actions.kt",
"chars": 197,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons\n\nimport io.kanro.compose.jetbrains.icons.JBIcons\n\ninternal object Actio"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__General.kt",
"chars": 197,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons\n\nimport io.kanro.compose.jetbrains.icons.JBIcons\n\ninternal object Gener"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpand.kt",
"chars": 1471,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.actions\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpandDark.kt",
"chars": 1499,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.actions\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Checkmark.kt",
"chars": 1995,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.actions\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/CheckmarkIndeterminate.kt",
"chars": 1891,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.actions\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Close.kt",
"chars": 1842,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.actions\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangle.kt",
"chars": 1508,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.general\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangleDark.kt",
"chars": 1536,
"preview": "package io.kanro.compose.jetbrains.icons.jbicons.general\n\nimport androidx.compose.ui.graphics.Color\nimport androidx.comp"
},
{
"path": "expui/build.gradle.kts",
"chars": 523,
"preview": "plugins {\n kotlin(\"jvm\")\n id(\"com.netflix.nebula.maven-publish\")\n id(\"com.netflix.nebula.source-jar\")\n id(\"c"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/DesktopPlatform.kt",
"chars": 488,
"preview": "package io.kanro.compose.jetbrains.expui\n\nenum class DesktopPlatform {\n Linux,\n Windows,\n MacOS,\n Unknown;\n\n"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ActionButton.kt",
"chars": 3236,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Button.kt",
"chars": 5554,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/CheckBox.kt",
"chars": 9780,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foun"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ComboBox.kt",
"chars": 6400,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ContextCompositionLocals.kt",
"chars": 158,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.runtime.compositionLocalOf\n\nval LocalContentAc"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ContextMenu.kt",
"chars": 8794,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.ContextMenuItem\nimport androidx.com"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/DropdownMenu.kt",
"chars": 15137,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.animation.core.MutableTransitionState\nimport a"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Icon.kt",
"chars": 4486,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Indication.kt",
"chars": 2489,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Label.kt",
"chars": 3877,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.text.BasicText\nimport androidx.comp"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Link.kt",
"chars": 12411,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Painter.kt",
"chars": 13228,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifi"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/PointerInput.kt",
"chars": 558,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.p"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ProgressBar.kt",
"chars": 3071,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.animation.core.animateFloat\nimport androidx.co"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/RadioButton.kt",
"chars": 5432,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foun"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/SegmentedButton.kt",
"chars": 8642,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.focusable\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Tab.kt",
"chars": 7717,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/TextArea.kt",
"chars": 8183,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.interaction.MutableInteractionSourc"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/TextField.kt",
"chars": 13861,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.interaction.MutableInteractionSourc"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ToolBar.kt",
"chars": 4313,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.Indication\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Tooltip.kt",
"chars": 3043,
"preview": "package io.kanro.compose.jetbrains.expui.control\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport an"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/AreaColors.kt",
"chars": 4016,
"preview": "package io.kanro.compose.jetbrains.expui.style\n\nimport androidx.compose.foundation.background\nimport androidx.compose.fo"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/AreaProvider.kt",
"chars": 1232,
"preview": "package io.kanro.compose.jetbrains.expui.style\n\ninterface AreaProvider {\n val normalAreaColors: AreaColors\n}\n\ninterfa"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/Border.kt",
"chars": 9163,
"preview": "package io.kanro.compose.jetbrains.expui.style\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/TextStyle.kt",
"chars": 1131,
"preview": "package io.kanro.compose.jetbrains.expui.style\n\nimport androidx.compose.runtime.compositionLocalOf\nimport io.kanro.compo"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/DarkTheme.kt",
"chars": 30271,
"preview": "package io.kanro.compose.jetbrains.expui.theme\n\nimport androidx.compose.foundation.LocalContextMenuRepresentation\nimport"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/Fonts.kt",
"chars": 1271,
"preview": "package io.kanro.compose.jetbrains.expui.theme\n\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/LightTheme.kt",
"chars": 30128,
"preview": "package io.kanro.compose.jetbrains.expui.theme\n\nimport androidx.compose.foundation.LocalContextMenuRepresentation\nimport"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/Theme.kt",
"chars": 572,
"preview": "package io.kanro.compose.jetbrains.expui.theme\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runti"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/CustomWindowDecorationAccessing.kt",
"chars": 2397,
"preview": "package io.kanro.compose.jetbrains.expui.util\n\nimport java.awt.Shape\nimport java.awt.Window\nimport java.lang.reflect.Met"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/UnsafeAccessing.kt",
"chars": 1920,
"preview": "package io.kanro.compose.jetbrains.expui.util\n\nimport sun.misc.Unsafe\nimport java.lang.reflect.AccessibleObject\n\ninterna"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.Linux.kt",
"chars": 2543,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.background\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.MacOS.kt",
"chars": 4153,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.background\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.Windows.kt",
"chars": 2714,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.background\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.kt",
"chars": 2608,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runt"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Basic.kt",
"chars": 1052,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx."
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Linux.kt",
"chars": 370,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.w"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.MacOS.kt",
"chars": 866,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compos"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Windows.kt",
"chars": 3940,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.f"
},
{
"path": "expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.kt",
"chars": 8909,
"preview": "package io.kanro.compose.jetbrains.expui.window\n\nimport androidx.compose.foundation.layout.LayoutScopeMarker\nimport andr"
},
{
"path": "expui-gallery/build.gradle.kts",
"chars": 679,
"preview": "plugins {\n kotlin(\"jvm\")\n id(\"org.jetbrains.compose\")\n}\n\ndependencies {\n implementation(\"org.jetbrains.kotlinx:"
},
{
"path": "expui-gallery/src/main/kotlin/Main.kt",
"chars": 15842,
"preview": "import androidx.compose.animation.core.animateFloat\nimport androidx.compose.animation.core.infiniteRepeatable\nimport and"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 200,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 26,
"preview": "kotlin.code.style=official"
},
{
"path": "gradlew",
"chars": 5774,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "settings.gradle.kts",
"chars": 409,
"preview": "pluginManagement {\n repositories {\n mavenLocal()\n gradlePluginPortal()\n maven(\"https://maven.pkg"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the ButterCam/compose-jetbrains-theme GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 104 files (428.5 KB), approximately 95.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.