├── .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
├── docs
├── gradle-jvm.png
├── project-sdk.png
├── screenshot-expui.png
└── screenshot.png
├── expui-gallery
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── Main.kt
│ └── resources
│ └── icons
│ ├── darkTheme.svg
│ ├── darkTheme_dark.svg
│ ├── generic.svg
│ ├── generic_dark.svg
│ ├── github.svg
│ ├── github_dark.svg
│ ├── kotlin.svg
│ ├── lightTheme.svg
│ ├── lightTheme_dark.svg
│ ├── settings.svg
│ ├── settings_dark.svg
│ ├── text.svg
│ └── text_dark.svg
├── 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
│ └── resources
│ ├── fonts
│ ├── Inter-Black.ttf
│ ├── Inter-Bold.ttf
│ ├── Inter-ExtraBold.ttf
│ ├── Inter-ExtraLight.ttf
│ ├── Inter-Light.ttf
│ ├── Inter-Medium.ttf
│ ├── Inter-Regular.ttf
│ ├── Inter-SemiBold.ttf
│ └── Inter-Thin.ttf
│ └── icons
│ ├── buttonDropTriangle.svg
│ ├── buttonDropTriangle_dark.svg
│ ├── closeSmall.svg
│ ├── closeSmallHovered.svg
│ ├── closeSmallHovered_dark.svg
│ ├── closeSmall_dark.svg
│ ├── compose.svg
│ ├── external_link_arrow.svg
│ ├── external_link_arrow_dark.svg
│ ├── linkDropTriangle.svg
│ ├── linkDropTriangle_dark.svg
│ └── windows
│ ├── closeActive.svg
│ ├── closeInactive.svg
│ ├── collapse.svg
│ ├── maximize.svg
│ ├── maximizeInactive.svg
│ ├── minimize.svg
│ ├── minimizeInactive.svg
│ ├── restore.svg
│ └── restoreInactive.svg
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests)
4 | push:
5 | branches: [ main ]
6 | # Trigger the workflow on any pull request
7 | pull_request:
8 |
9 | jobs:
10 | # Run Gradle Wrapper Validation Action to verify the wrapper's checksum
11 | gradleValidation:
12 | name: Gradle Wrapper
13 | runs-on: ubuntu-latest
14 | steps:
15 |
16 | # Check out current repository
17 | - name: Fetch Sources
18 | uses: actions/checkout@v2.3.4
19 |
20 | # Validate wrapper
21 | - name: Gradle Wrapper Validation
22 | uses: gradle/wrapper-validation-action@v1.0.4
23 | build:
24 | name: Build
25 | runs-on: ubuntu-latest
26 | outputs:
27 | version: ${{ steps.properties.outputs.version }}
28 | changelog: ${{ steps.properties.outputs.changelog }}
29 | steps:
30 |
31 | # Check out current repository
32 | - name: Fetch Sources
33 | uses: actions/checkout@v2
34 |
35 | - name: Download JBR
36 | uses: carlosperate/download-file-action@v2
37 | with:
38 | file-url: 'https://cache-redirector.jetbrains.com/intellij-jbr/jbr-17.0.5-linux-x64-b653.14.tar.gz'
39 | file-name: 'jbr-17.0.5.tar.gz'
40 |
41 | - name: Setup Java
42 | uses: actions/setup-java@v3
43 | with:
44 | java-version: 17
45 | distribution: jdkfile
46 | jdkFile: jbr-17.0.5.tar.gz
47 | cache: gradle
48 |
49 | # Set environment variables
50 | - name: Export Properties
51 | id: properties
52 | shell: bash
53 | run: |
54 | PROPERTIES="$(./gradlew properties -q)"
55 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')"
56 | NAME="$(echo "$PROPERTIES" | grep "^name:" | cut -f2- -d ' ')"
57 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)"
58 | CHANGELOG="${CHANGELOG//'%'/'%25'}"
59 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
60 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
61 | echo "version=$VERSION"
62 | echo "name=$NAME"
63 | echo "changelog=$CHANGELOG"
64 | echo "version=$VERSION" >> $GITHUB_OUTPUT
65 | echo "name=$NAME" >> $GITHUB_OUTPUT
66 | echo "changelog=$CHANGELOG" >> $GITHUB_OUTPUT
67 | # Build artifact using buildPlugin Gradle task
68 | - name: Build
69 | run: ./gradlew build
70 |
71 | # Store built plugin as an artifact for downloading
72 | - name: Upload artifacts
73 | uses: actions/upload-artifact@v3
74 | with:
75 | name: "${{ steps.properties.outputs.name }} - ${{ steps.properties.outputs.version }}"
76 | path: ./**/build/libs/*
77 |
78 | # Prepare a draft release for GitHub Releases page for the manual verification
79 | # If accepted and published, release workflow would be triggered
80 | releaseDraft:
81 | name: Release Draft
82 | if: github.event_name != 'pull_request'
83 | needs: build
84 | runs-on: ubuntu-latest
85 | steps:
86 |
87 | # Check out current repository
88 | - name: Fetch Sources
89 | uses: actions/checkout@v2.3.4
90 |
91 | # Remove old release drafts by using the curl request for the available releases with draft flag
92 | - name: Remove Old Release Drafts
93 | env:
94 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95 | run: |
96 | gh api repos/{owner}/{repo}/releases \
97 | --jq '.[] | select(.draft == true) | .id' \
98 | | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{}
99 | # Create new release draft - which is not publicly visible and requires manual acceptance
100 | - name: Create Release Draft
101 | env:
102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
103 | run: |
104 | gh release create v${{ needs.build.outputs.version }} \
105 | --draft \
106 | --title "v${{ needs.build.outputs.version }}" \
107 | --notes "${{ needs.build.outputs.changelog }}"
108 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # GitHub Actions Workflow created for handling the release process based on the draft release prepared
2 | # with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided.
3 |
4 | name: Release
5 | on:
6 | release:
7 | types: [ prereleased, released ]
8 |
9 | jobs:
10 |
11 | # Prepare and publish the plugin to the Marketplace repository
12 | release:
13 | name: Publish Plugin
14 | runs-on: ubuntu-latest
15 | steps:
16 |
17 | # Check out current repository
18 | - name: Fetch Sources
19 | uses: actions/checkout@v2.3.4
20 | with:
21 | ref: ${{ github.event.release.tag_name }}
22 |
23 | - name: Download JBR
24 | uses: carlosperate/download-file-action@v2
25 | with:
26 | file-url: 'https://cache-redirector.jetbrains.com/intellij-jbr/jbr-17.0.5-linux-x64-b653.14.tar.gz'
27 | file-name: 'jbr-17.0.5.tar.gz'
28 |
29 | - name: Setup Java
30 | uses: actions/setup-java@v3
31 | with:
32 | java-version: 17
33 | distribution: jdkfile
34 | jdkFile: jbr-17.0.5.tar.gz
35 | cache: gradle
36 |
37 | # Update Unreleased section with the current release note
38 | - name: Patch Changelog
39 | run: |
40 | ./gradlew patchChangelog --release-note="`cat << EOM
41 | ${{ github.event.release.body }}
42 | EOM`"
43 |
44 | - name: Import GPG key
45 | uses: crazy-max/ghaction-import-gpg@v3
46 | with:
47 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
48 |
49 | - uses: ButterCam/setup-sisyphus-build@v1
50 | with:
51 | dependency-repositories: local,central,portal,google,snapshot
52 | snapshot-url: https://s01.oss.sonatype.org/content/repositories/snapshots
53 | snapshot-username: ${{ secrets.OSSRH_USERNAME }}
54 | snapshot-password: ${{ secrets.OSSRH_PASSWORD }}
55 | release-url: https://s01.oss.sonatype.org/service/local/staging/deploy/maven2
56 | release-username: ${{ secrets.OSSRH_USERNAME }}
57 | release-password: ${{ secrets.OSSRH_PASSWORD }}
58 | gradle-portal-key: ${{ secrets.GRADLE_PUBLISH_KEY }}
59 | gradle-portal-secret: ${{ secrets.GRADLE_PUBLISH_SECRET }}
60 | gpg-key-name: ${{ secrets.GPG_KEY_NAME }}
61 |
62 | # Publish the plugin to the Marketplace
63 | - name: Publish
64 | env:
65 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
66 | run: ./gradlew publish
67 |
68 | # Upload artifact as a release asset
69 | - name: Upload Release Asset
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | run: gh release upload ${{ github.event.release.tag_name }} ./**/build/libs/*
73 |
74 | # Create pull request
75 | - name: Create Pull Request
76 | env:
77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78 | run: |
79 | VERSION="${{ github.event.release.tag_name }}"
80 | BRANCH="changelog-update-$VERSION"
81 | git config user.email "action@github.com"
82 | git config user.name "GitHub Action"
83 | git checkout -b $BRANCH
84 | git commit -am "Changelog update - $VERSION"
85 | git push --set-upstream origin $BRANCH
86 | gh pr create \
87 | --title ":bookmark: Changelog update - \`$VERSION\`" \
88 | --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \
89 | --base main \
90 | --head $BRANCH
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /build/
3 | !foundation/gradle/wrapper/gradle-wrapper.jar
4 |
5 | ### STS ###
6 | .apt_generated
7 | .classpath
8 | .factorypath
9 | .project
10 | .settings
11 | .springBeans
12 |
13 | ### IntelliJ IDEA ###
14 | .idea
15 | *.iws
16 | *.iml
17 | *.ipr
18 |
19 | ### NetBeans ###
20 | nbproject/private/
21 | build/
22 | nbbuild/
23 | dist/
24 | nbdist/
25 | .nb-gradle/
26 |
27 | logs/
28 | out/
29 | data/elasticsearch
30 | .DS_Store
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased]
4 |
5 | ## [2.2.0]
6 | - Upgrade to compose 1.5.2
7 |
8 | ## [2.1.0]
9 | - Provide BasicMainToolBar #18
10 | - Disable minimize and maximize button when resizeable is false #19
11 | - Fix the custom title bar when MainToolBar Content is empty #20
12 | - Optimize the implementation of themes. Switching themes will not cause full recompose.
13 | - Add text styles for theme
14 |
15 | ## [2.0.0]
16 | - Exp UI theme support
17 |
18 | ## [1.1.0]
19 | - Target to jvm 1.11 #11
20 | - Replacing SVG Resources with Compose generated Image Vectors #13
21 | - IntelliJ Dracula colors (Dark Theme) #12
22 |
23 | Thanks @DevSrSouza
24 |
25 | ## [1.0.1]
26 | - Upgrade to compose 1.0.1
27 |
28 | ## [1.0.0]
29 | - JetBrains UI Kit for Compose desktop
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 ButterCam
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # We are planning to migrate to [JetBrains/jewel](https://github.com/JetBrains/jewel)!
2 |
3 | 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.
4 |
5 | Track progress in this issue: JetBrains/jewel#28
6 |
7 | # JetBrains UI Kit for Compose Desktop
8 |
9 | New JetBrains style controls and UI kits for [Compose Desktop](https://www.jetbrains.com/lp/compose/).
10 |
11 | Classic JetBrains UI kit have been moved to [here](classic).
12 |
13 | 
14 |
15 | ## Quick Start
16 |
17 | ## Requirements
18 |
19 | ### JetBrains Runtime
20 |
21 | To get the best presentation across platforms, this library requires the application to run
22 | on [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime) (JBR).
23 |
24 | [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime) is a modified version of the JetBrains JDK
25 | distribution, which is widely used across the JetBrains' IDEs such as IntelliJ, GoLand and others.
26 |
27 | In this library we use the additional API provided by JetBrains Runtime to customise the title bar in Windows and macOS.
28 |
29 | If the `JBWindow` component is not used, it can also be used without the JetBrains Runtime.
30 |
31 | When using `JBWindow` in a non-JetBrains Runtime environment, a native title bar may additionally be displayed.
32 |
33 | JBR-17 (corresponding to JDK 17) can be downloaded from [the SDK page](https://www.jetbrains.com/help/idea/sdk.html) of
34 | IntelliJ.
35 |
36 | Make sure that the [project SDK](https://www.jetbrains.com/help/idea/project-settings-and-structure.html#project-sdk)
37 | and [Gradle JVM](https://www.jetbrains.com/help/idea/gradle-jvm-selection.html) are both JBR-17.
38 |
39 | #### Project SDK settings
40 |
41 | 
42 |
43 | #### Gradle JVM settings
44 |
45 | 
46 |
47 | ### `jdk.unsupported` module
48 |
49 | Most of the JetBrains Runtime APIs are private to JetBrains, and the class `sun.misc.Unsafe` is used to get access to
50 | these APIs.
51 |
52 | You need to add the `jdk.unsupported` module to the compose dependency as in the following code to make it work.
53 |
54 | You can also skip this step if you don't need to use the `JBWindow` component.
55 |
56 | ```kotlin
57 | compose.desktop {
58 | application {
59 | nativeDistributions {
60 | modules("jdk.unsupported")
61 | }
62 | }
63 | }
64 | ```
65 |
66 | ## 1. Add dependency
67 |
68 | ```kotlin
69 | dependencies {
70 | implementation(compose.desktop.currentOs) {
71 | exclude("org.jetbrains.compose.material")
72 | }
73 | implementation("com.bybutter.compose:compose-jetbrains-expui-theme:2.0.0")
74 | }
75 | ```
76 |
77 | ## 2. Use JBWindow DSL
78 |
79 | The optimal display of JBWindow requires JetBrains Runtime, refer to the previous [requirements](#Requirements).
80 |
81 | ```kotlin
82 | fun main() = application {
83 | JBWindow(
84 | title = "JetBrains ExpUI Gallery",
85 | 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).
86 | theme = LightTheme, // Change the theme here, LightTheme and DarkTheme are provided.
87 | state = rememberWindowState(size = DpSize(900.dp, 700.dp)),
88 | onCloseRequest = {
89 | exitApplication()
90 | },
91 | mainToolBar = {
92 | // Render your own component in the MainToolBar (TitleBar).
93 | Row(
94 | Modifier.mainToolBarItem(
95 | Alignment.End,
96 | true
97 | )
98 | ) { // Use the mainToolBarItem modifier to change alignment of components and enable/disable window drag area on this component.
99 | }
100 | }) {
101 | // Window content
102 | }
103 | }
104 | ```
105 |
106 | ## 3. Use LightTheme and DarkTheme DSL
107 |
108 | Since `JBWindow` requires JetBrains Runtime, if you don't want to use the `JBWindow` component, you can also use a
109 | normal `Window` with `LightTheme`/`DarkTheme` DSL.
110 |
111 | ```kotlin
112 | fun main() = application {
113 | Window({}) {
114 | LightTheme {
115 | // Your components here
116 | }
117 | }
118 | }
119 | ```
120 |
121 |
122 | # Screenshot
123 |
124 | ## MacOS
125 |
126 |
127 |
128 |
129 | ## Windows
130 |
131 | 
132 | 
133 |
134 | ## Linux(Ubuntu with Gnome)
135 |
136 | 
137 | 
138 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.bybutter.sisyphus.project") version "2.1.0" apply false
3 | id("com.netflix.nebula.contacts") version "7.0.1"
4 | id("com.netflix.nebula.info") version "12.1.6" apply false
5 | id("com.netflix.nebula.maven-publish") version "20.3.0" apply false
6 | id("com.netflix.nebula.source-jar") version "20.3.0" apply false
7 | id("org.jetbrains.changelog") version "1.3.0"
8 | id("org.jetbrains.compose") version "1.5.2" apply false
9 | kotlin("jvm") version "1.9.10" apply false
10 | }
11 |
12 | allprojects {
13 | apply(plugin = "com.netflix.nebula.contacts")
14 | apply(plugin = "com.netflix.nebula.info")
15 |
16 | group = "com.bybutter.compose"
17 | version = "2.2.0"
18 |
19 | contacts {
20 | addPerson(
21 | "higan@live.cn",
22 | delegateClosureOf {
23 | moniker = "higan"
24 | github = "devkanro"
25 | roles.add("owner")
26 | },
27 | )
28 | }
29 | }
30 |
31 | subprojects {
32 | apply(plugin = "org.jetbrains.compose")
33 |
34 | tasks.withType() {
35 | kotlinOptions.jvmTarget = "17"
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | google()
42 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
43 | }
44 | }
45 |
46 | changelog {
47 | version.set(project.version.toString())
48 | groups.set(emptyList())
49 | }
50 |
--------------------------------------------------------------------------------
/classic/README.md:
--------------------------------------------------------------------------------
1 | # JetBrains UI Kit for Compose Desktop
2 |
3 | JetBrains style controls and UI for [Compose Desktop](https://www.jetbrains.com/lp/compose/).
4 |
5 | 
6 |
7 | ## Quick Start
8 |
9 | ## 1. Add dependency
10 |
11 | ```kotlin
12 | dependencies {
13 | implementation(compose.desktop.currentOs) {
14 | exclude("org.jetbrains.compose.material")
15 | }
16 | implementation("com.bybutter.compose:compose-jetbrains-theme:2.0.0")
17 | }
18 | ```
19 |
20 | ## 2. JBTheme DSL
21 |
22 | ```kotlin
23 | fun main() = application {
24 | Window(
25 | onCloseRequest = ::exitApplication,
26 | title = "Compose for Desktop",
27 | state = rememberWindowState(width = 300.dp, height = 300.dp)
28 | ) {
29 | val count = remember { mutableStateOf(0) }
30 |
31 | JBTheme {
32 | JPanel(Modifier.fillMaxSize().jBorder(top = 1.dp, color = JBTheme.panelColors.border)) {
33 | Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
34 | Button(modifier = Modifier.align(Alignment.CenterHorizontally),
35 | onClick = {
36 | count.value++
37 | }) {
38 | Text(if (count.value == 0) "Hello World" else "Clicked ${count.value}!")
39 | }
40 | Button(modifier = Modifier.align(Alignment.CenterHorizontally),
41 | onClick = {
42 | count.value = 0
43 | }) {
44 | Text("Reset")
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | ```
--------------------------------------------------------------------------------
/classic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | `java-library`
4 | id("com.netflix.nebula.maven-publish")
5 | id("com.netflix.nebula.source-jar")
6 | id("com.bybutter.sisyphus.project")
7 | id("org.jetbrains.compose")
8 | }
9 |
10 | description = "JetBrains UI Kit for Compose Desktop"
11 |
12 | dependencies {
13 | implementation(kotlin("stdlib"))
14 | implementation(compose.desktop.common) {
15 | exclude("org.jetbrains.compose.material")
16 | }
17 | }
18 |
19 | tasks.withType() {
20 | kotlinOptions.jvmTarget = "17"
21 | kotlinOptions.freeCompilerArgs += listOf(
22 | // "-P", "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/JBTypography.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.text.style.TextDecoration
8 | import androidx.compose.ui.unit.sp
9 |
10 | class JBTypography(
11 | val h0: TextStyle,
12 | val h1: TextStyle,
13 | val h2: TextStyle,
14 | val h2Bold: TextStyle,
15 | val h3: TextStyle,
16 | val h3Bold: TextStyle,
17 | val default: TextStyle,
18 | val defaultBold: TextStyle,
19 | val defaultUnderlined: TextStyle,
20 | val paragraph: TextStyle,
21 | val medium: TextStyle,
22 | val mediumBold: TextStyle,
23 | val small: TextStyle,
24 | val smallUnderlined: TextStyle,
25 | ) {
26 | constructor(
27 | defaultFontFamily: FontFamily = FontFamily.Default,
28 | h0: TextStyle = TextStyle(
29 | fontWeight = FontWeight.Medium,
30 | fontSize = 25.sp
31 | ),
32 | h1: TextStyle = TextStyle(
33 | fontWeight = FontWeight.Medium,
34 | fontSize = 22.sp
35 | ),
36 | h2: TextStyle = TextStyle(
37 | fontWeight = FontWeight.Normal,
38 | fontSize = 18.sp,
39 | ),
40 | h2Bold: TextStyle = h2.copy(fontWeight = FontWeight.Medium),
41 | h3: TextStyle = TextStyle(
42 | fontWeight = FontWeight.Normal,
43 | fontSize = 16.sp,
44 | lineHeight = 20.sp
45 | ),
46 | h3Bold: TextStyle = h3.copy(fontWeight = FontWeight.Medium),
47 | default: TextStyle = TextStyle(
48 | fontWeight = FontWeight.Normal,
49 | fontSize = 12.5.sp,
50 | lineHeight = 15.sp
51 | ),
52 | defaultBold: TextStyle = default.copy(fontWeight = FontWeight.Medium),
53 | defaultUnderlined: TextStyle = default.copy(textDecoration = TextDecoration.Underline),
54 | paragraph: TextStyle = TextStyle(
55 | fontWeight = FontWeight.Normal,
56 | fontSize = 13.sp,
57 | lineHeight = 19.sp
58 | ),
59 | medium: TextStyle = TextStyle(
60 | fontWeight = FontWeight.Normal,
61 | fontSize = 12.sp,
62 | lineHeight = 15.sp
63 | ),
64 | mediumBold: TextStyle = medium.copy(fontWeight = FontWeight.Medium),
65 | small: TextStyle = TextStyle(
66 | fontWeight = FontWeight.Normal,
67 | fontSize = 11.sp,
68 | lineHeight = 14.sp
69 | ),
70 | smallUnderlined: TextStyle = small.copy(textDecoration = TextDecoration.Underline),
71 | ) : this(
72 | h0.withDefaultFontFamily(defaultFontFamily),
73 | h1.withDefaultFontFamily(defaultFontFamily),
74 | h2.withDefaultFontFamily(defaultFontFamily),
75 | h2Bold.withDefaultFontFamily(defaultFontFamily),
76 | h3.withDefaultFontFamily(defaultFontFamily),
77 | h3Bold.withDefaultFontFamily(defaultFontFamily),
78 | default.withDefaultFontFamily(defaultFontFamily),
79 | defaultBold.withDefaultFontFamily(defaultFontFamily),
80 | defaultUnderlined.withDefaultFontFamily(defaultFontFamily),
81 | paragraph.withDefaultFontFamily(defaultFontFamily),
82 | medium.withDefaultFontFamily(defaultFontFamily),
83 | mediumBold.withDefaultFontFamily(defaultFontFamily),
84 | small.withDefaultFontFamily(defaultFontFamily),
85 | smallUnderlined.withDefaultFontFamily(defaultFontFamily)
86 | )
87 |
88 | companion object {
89 | private fun TextStyle.withDefaultFontFamily(default: FontFamily): TextStyle {
90 | return if (fontFamily != null) this else copy(fontFamily = default)
91 | }
92 | }
93 | }
94 |
95 | val LocalTypography = compositionLocalOf { JBTypography() }
96 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/CheckBoxColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class CheckBoxColors(
10 | bg: Color,
11 | bgSelected: Color,
12 | bgDisabled: Color,
13 | border: Color,
14 | borderSelected: Color,
15 | borderFocused: Color,
16 | borderDisabled: Color,
17 | ) {
18 | var bg by mutableStateOf(bg)
19 | var bgSelected by mutableStateOf(bgSelected)
20 | var bgDisabled by mutableStateOf(bgDisabled)
21 | var border by mutableStateOf(border)
22 | var borderSelected by mutableStateOf(borderSelected)
23 | var borderFocused by mutableStateOf(borderFocused)
24 | var borderDisabled by mutableStateOf(borderDisabled)
25 |
26 | fun copy(
27 | bg: Color = this.bg,
28 | bgSelected: Color = this.bgSelected,
29 | bgDisabled: Color = this.bgDisabled,
30 | border: Color = this.border,
31 | borderSelected: Color = this.borderSelected,
32 | borderFocused: Color = this.borderFocused,
33 | borderDisabled: Color = this.borderDisabled,
34 | ): CheckBoxColors {
35 | return CheckBoxColors(
36 | bg,
37 | bgSelected,
38 | bgDisabled,
39 | border,
40 | borderSelected,
41 | borderFocused,
42 | borderDisabled
43 | )
44 | }
45 | }
46 |
47 | fun lightCheckBoxColors(): CheckBoxColors {
48 | return CheckBoxColors(
49 | Color.White,
50 | Color(0xFF4F9EE3),
51 | Color(0xFFF2F2F2),
52 | Color(0xFFB0B0B0),
53 | Color(0xFF4B97D9),
54 | Color(0xFF7B9FC7),
55 | Color(0xFFBDBDBD),
56 | )
57 | }
58 |
59 | fun darkCheckBoxColors(): CheckBoxColors {
60 | return CheckBoxColors(
61 | Color(0xFF43494A),
62 | Color(0xFF43494A),
63 | Color(0xFF3C3F41),
64 | Color(0xFF6B6B6B),
65 | Color(0xFF4C708C),
66 | Color(0xFF43698E),
67 | Color(0xFF545556),
68 | )
69 | }
70 |
71 | val LocalCheckBoxColors = compositionLocalOf { lightCheckBoxColors() }
72 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FieldColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.foundation.interaction.InteractionSource
6 | import androidx.compose.foundation.interaction.collectIsFocusedAsState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.State
9 | import androidx.compose.runtime.compositionLocalOf
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.rememberUpdatedState
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.graphics.Color
15 | import io.kanro.compose.jetbrains.JBTheme
16 |
17 | class FieldColors(
18 | bg: Color,
19 | border: Color,
20 | borderFocused: Color,
21 | comboboxButton: Color,
22 | bgDisabled: Color,
23 | borderDisabled: Color,
24 | borderError: Color,
25 | ) {
26 | var bg by mutableStateOf(bg)
27 | var border by mutableStateOf(border)
28 | var borderFocused by mutableStateOf(borderFocused)
29 | var comboboxButton by mutableStateOf(comboboxButton)
30 | var bgDisabled by mutableStateOf(bgDisabled)
31 | var borderDisabled by mutableStateOf(borderDisabled)
32 | var borderError by mutableStateOf(borderError)
33 |
34 | fun copy(
35 | bg: Color = this.bg,
36 | border: Color = this.border,
37 | borderFocused: Color = this.borderFocused,
38 | comboboxButton: Color = this.comboboxButton,
39 | bgDisabled: Color = this.bgDisabled,
40 | borderDisabled: Color = this.borderDisabled,
41 | borderError: Color = this.borderError,
42 | ): FieldColors {
43 | return FieldColors(bg, border, borderFocused, comboboxButton, bgDisabled, borderDisabled, borderError)
44 | }
45 |
46 | @Composable
47 | fun bgColor(enable: Boolean): State {
48 | return rememberUpdatedState(if (enable) bg else bgDisabled)
49 | }
50 |
51 | @Composable
52 | fun borderColor(enable: Boolean, error: Boolean, interactionSource: InteractionSource): State {
53 | val focused by interactionSource.collectIsFocusedAsState()
54 | val targetValue = when {
55 | !enable -> borderDisabled
56 | error -> borderError
57 | focused -> borderFocused
58 | else -> border
59 | }
60 | return if (enable) {
61 | animateColorAsState(targetValue, tween(durationMillis = AnimationDuration))
62 | } else {
63 | rememberUpdatedState(targetValue)
64 | }
65 | }
66 |
67 | @Composable
68 | fun focusingColor(enable: Boolean, error: Boolean, interactionSource: InteractionSource): State {
69 | val focused by interactionSource.collectIsFocusedAsState()
70 | val targetValue = when {
71 | !enable -> Color.Transparent
72 | error -> JBTheme.focusColors.error
73 | focused -> JBTheme.focusColors.default
74 | else -> Color.Transparent
75 | }
76 | return rememberUpdatedState(targetValue)
77 | }
78 |
79 | companion object {
80 | internal const val AnimationDuration = 150
81 | }
82 | }
83 |
84 | fun lightFieldColors(): FieldColors {
85 | return FieldColors(
86 | Color.White,
87 | Color(0xFFC4C4C4),
88 | Color(0xFF87AFDA),
89 | Color(0xFFFAFAFA),
90 | Color(0xFFF2F2F2),
91 | Color(0xFFCFCFCF),
92 | Color(0xFFCE3845),
93 | )
94 | }
95 |
96 | fun darkFieldColors(): FieldColors {
97 | return FieldColors(
98 | Color(0xFF4C5052),
99 | Color(0xFF5E6060),
100 | Color(0xFF456A90),
101 | Color(0xFF3C3F41),
102 | Color(0xFF3C3F41),
103 | Color(0xFF646464),
104 | Color(0xFF73454B),
105 | )
106 | }
107 |
108 | val LocalFieldColors = compositionLocalOf { lightFieldColors() }
109 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/FocusColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class FocusColors(
10 | default: Color,
11 | error: Color,
12 | warning: Color,
13 | warningInactive: Color,
14 | ) {
15 | var default by mutableStateOf(default)
16 | var error by mutableStateOf(error)
17 | var warning by mutableStateOf(warning)
18 | var warningInactive by mutableStateOf(warningInactive)
19 |
20 | fun copy(
21 | default: Color = this.default,
22 | error: Color = this.error,
23 | warning: Color = this.warning,
24 | warningInactive: Color = this.warningInactive,
25 | ): FocusColors {
26 | return FocusColors(default, error, warning, warningInactive)
27 | }
28 | }
29 |
30 | fun lightFocusColors(): FocusColors {
31 | return FocusColors(
32 | Color(0xFF97C3F3),
33 | Color(0xFFE53E4D),
34 | Color(0xFFE1A336),
35 | Color(0xFFEAD2A1),
36 | )
37 | }
38 |
39 | fun darkFocusColors(): FocusColors {
40 | return FocusColors(
41 | Color(0xFF3D6185),
42 | Color(0xFF8B3C3C),
43 | Color(0xFFAC7920),
44 | Color(0xFF6E5324),
45 | )
46 | }
47 |
48 | val LocalFocusColors = compositionLocalOf { lightFocusColors() }
49 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/IconColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class IconColors(
10 | selected: Color,
11 | disabled: Color,
12 | ) {
13 | var selected by mutableStateOf(selected)
14 | var disabled by mutableStateOf(disabled)
15 |
16 | fun copy(
17 | selected: Color = this.selected,
18 | disabled: Color = this.disabled,
19 | ): IconColors {
20 | return IconColors(
21 | selected,
22 | disabled,
23 | )
24 | }
25 | }
26 |
27 | fun lightIconColors(): IconColors {
28 | return IconColors(
29 | Color.White,
30 | Color(0xFFABABAB),
31 | )
32 | }
33 |
34 | fun darkIconColors(): IconColors {
35 | return IconColors(
36 | Color(0xFFFEFEFE),
37 | Color(0xFFABABAB),
38 | )
39 | }
40 |
41 | val LocalIconColors = compositionLocalOf { lightIconColors() }
42 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/PanelColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class PanelColors(
10 | border: Color,
11 | bgContent: Color,
12 | bgDialog: Color,
13 | ) {
14 | var border by mutableStateOf(border)
15 | var bgContent by mutableStateOf(bgContent)
16 | var bgDialog by mutableStateOf(bgDialog)
17 |
18 | fun copy(
19 | border: Color = this.border,
20 | bgContent: Color = this.bgContent,
21 | bgDialog: Color = this.bgDialog,
22 | ): PanelColors {
23 | return PanelColors(border, bgContent, bgDialog)
24 | }
25 | }
26 |
27 | fun lightPanelColors(): PanelColors {
28 | return PanelColors(
29 | Color(0xFFD1D1D1),
30 | Color.White,
31 | Color(0xFFF2F2F2),
32 | )
33 | }
34 |
35 | fun darkPanelColors(): PanelColors {
36 | return PanelColors(
37 | Color(0xFF323232),
38 | Color(0xFF3C3F41),
39 | Color(0xFF3C3F41),
40 | )
41 | }
42 |
43 | val LocalPanelColors = compositionLocalOf { lightPanelColors() }
44 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ProgressColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class ProgressColors(
10 | progress: Color,
11 | bg: Color,
12 | ) {
13 | var progress by mutableStateOf(progress)
14 | var bg by mutableStateOf(bg)
15 |
16 | fun copy(
17 | progress: Color = this.progress,
18 | bg: Color = this.bg,
19 | ): ProgressColors {
20 | return ProgressColors(progress, bg)
21 | }
22 | }
23 |
24 | fun lightProgressColors(): ProgressColors {
25 | return ProgressColors(
26 | Color(0xFF1E82E6),
27 | Color(0xFFD5D5D5),
28 | )
29 | }
30 |
31 | fun darkProgressColors(): ProgressColors {
32 | return ProgressColors(
33 | Color(0xFFA0A0A0),
34 | Color(0xFF555555),
35 | )
36 | }
37 |
38 | val LocalProgressColors = compositionLocalOf { lightProgressColors() }
39 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ScrollColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.foundation.ScrollbarStyle
4 | import androidx.compose.foundation.shape.CircleShape
5 | import androidx.compose.runtime.compositionLocalOf
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.setValue
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.unit.dp
11 |
12 | class ScrollColors(
13 | bg: Color,
14 | ) {
15 | var bg by mutableStateOf(bg)
16 |
17 | fun copy(
18 | bg: Color = this.bg,
19 | ): ScrollColors {
20 | return ScrollColors(bg)
21 | }
22 |
23 | fun style(): ScrollbarStyle {
24 | return ScrollbarStyle(
25 | minimalHeight = 30.dp,
26 | thickness = 7.dp,
27 | shape = CircleShape,
28 | hoverDurationMillis = 0,
29 | unhoverColor = bg,
30 | hoverColor = bg
31 | )
32 | }
33 | }
34 |
35 | fun lightScrollColors(): ScrollColors {
36 | return ScrollColors(
37 | Color(0xFFC9C9C9),
38 | )
39 | }
40 |
41 | fun darkScrollColors(): ScrollColors {
42 | return ScrollColors(
43 | Color(0xFF494949)
44 | )
45 | }
46 |
47 | val LocalScrollColors = compositionLocalOf { lightScrollColors() }
48 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/SelectionColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class SelectionColors(
10 | active: Color,
11 | inactive: Color,
12 | hover: Color,
13 | lightActive: Color,
14 | lightInactive: Color,
15 | completionPopup: Color,
16 | ) {
17 | var active by mutableStateOf(active)
18 | var inactive by mutableStateOf(inactive)
19 | var hover by mutableStateOf(hover)
20 | var lightActive by mutableStateOf(lightActive)
21 | var lightInactive by mutableStateOf(lightInactive)
22 | var completionPopup by mutableStateOf(completionPopup)
23 |
24 | fun copy(
25 | active: Color = this.active,
26 | inactive: Color = this.inactive,
27 | hover: Color = this.hover,
28 | lightActive: Color = this.lightActive,
29 | lightInactive: Color = this.lightInactive,
30 | completionPopup: Color = this.completionPopup,
31 | ): SelectionColors {
32 | return SelectionColors(active, inactive, hover, lightActive, lightInactive, completionPopup)
33 | }
34 | }
35 |
36 | fun lightSelectionColors(): SelectionColors {
37 | return SelectionColors(
38 | Color(0xFF2675BF),
39 | Color(0xFFD5D5D5),
40 | Color(0xFFEDF5FC),
41 | Color(0xFFEDF6FE),
42 | Color(0xFFF5F5F5),
43 | Color(0xFFC5DFFC),
44 | )
45 | }
46 |
47 | fun darkSelectionColors(): SelectionColors {
48 | return SelectionColors(
49 | Color(0xFF2F65CA),
50 | Color(0xFF0D293E),
51 | Color(0xFF464A4D),
52 | Color(0xFF464A4D),
53 | Color(0xFF35383B),
54 | Color(0xFF113A5C),
55 | )
56 | }
57 |
58 | val LocalSelectionColors = compositionLocalOf { lightSelectionColors() }
59 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TabColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class TabColors(
10 | selection: Color,
11 | focus: Color,
12 | selectionInactive: Color,
13 | hover: Color,
14 | selectionDisabled: Color,
15 | bgSelected: Color,
16 | ) {
17 | var selection by mutableStateOf(selection)
18 | var focus by mutableStateOf(focus)
19 | var selectionInactive by mutableStateOf(selectionInactive)
20 | var hover by mutableStateOf(hover)
21 | var selectionDisabled by mutableStateOf(selectionDisabled)
22 | var bgSelected by mutableStateOf(bgSelected)
23 |
24 | fun copy(
25 | selection: Color = this.selection,
26 | focus: Color = this.focus,
27 | selectionInactive: Color = this.selectionInactive,
28 | hover: Color = this.hover,
29 | selectionDisabled: Color = this.selectionDisabled,
30 | bgSelected: Color = this.bgSelected,
31 | ): TabColors {
32 | return TabColors(selection, focus, selectionInactive, hover, selectionDisabled, bgSelected)
33 | }
34 | }
35 |
36 | fun lightTabColors(): TabColors {
37 | return TabColors(
38 | Color(0xFF4083C9),
39 | Color(0xFFDAE4ED),
40 | Color(0xFF9CA7B8),
41 | Color(0x19000000),
42 | Color(0xFFABABAB),
43 | Color(0xFFFFFFFF),
44 | )
45 | }
46 |
47 | fun darkTabColors(): TabColors {
48 | return TabColors(
49 | Color(0xFF4A88C7),
50 | Color(0xFF3D4B5C),
51 | Color(0xFF747A80),
52 | Color(0xFF2E3133),
53 | Color(0xFF595959),
54 | Color(0xFF4E5254),
55 | )
56 | }
57 |
58 | val LocalTabColors = compositionLocalOf { lightTabColors() }
59 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TableColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.compose.ui.graphics.Color
7 |
8 | class TableColors(
9 | bg: Color,
10 | headerBorder: Color,
11 | outerBorder: Color,
12 | ) {
13 | var bg by mutableStateOf(bg)
14 | var headerBorder by mutableStateOf(headerBorder)
15 | var outerBorder by mutableStateOf(outerBorder)
16 | }
17 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/TextColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 |
9 | class TextColors(
10 | default: Color,
11 | disabled: Color,
12 | white: Color,
13 | link: Color,
14 | infoPanel: Color,
15 | infoInput: Color,
16 | error: Color,
17 | success: Color,
18 | ) {
19 | var default by mutableStateOf(default)
20 | var disabled by mutableStateOf(disabled)
21 | var white by mutableStateOf(white)
22 | var link by mutableStateOf(link)
23 | var infoPanel by mutableStateOf(infoPanel)
24 | var infoInput by mutableStateOf(infoInput)
25 | var error by mutableStateOf(error)
26 | var success by mutableStateOf(success)
27 |
28 | fun copy(
29 | default: Color = this.default,
30 | disabled: Color = this.disabled,
31 | white: Color = this.white,
32 | link: Color = this.link,
33 | infoPanel: Color = this.infoPanel,
34 | infoInput: Color = this.infoInput,
35 | error: Color = this.error,
36 | success: Color = this.success,
37 | ): TextColors {
38 | return TextColors(default, disabled, white, link, infoPanel, infoInput, error, success)
39 | }
40 | }
41 |
42 | fun lightTextColors(): TextColors {
43 | return TextColors(
44 | Color.Black,
45 | Color(0xFF8C8C8C),
46 | Color.White,
47 | Color(0xFF2470B3),
48 | Color(0xFF808080),
49 | Color(0xFF999999),
50 | Color(0xFFC7222D),
51 | Color(0xFF368746),
52 | )
53 | }
54 |
55 | fun darkTextColors(): TextColors {
56 | return TextColors(
57 | Color(0xFFBBBBBB),
58 | Color(0xFF777777),
59 | Color(0xFFFEFEFE),
60 | Color(0xFF589DF6),
61 | Color(0xFF8C8C8C),
62 | Color(0xFF787878),
63 | Color(0xFFFF5261),
64 | Color(0xFF50A661),
65 | )
66 | }
67 |
68 | val LocalTextColors = compositionLocalOf { lightTextColors() }
69 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToggleColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.compose.ui.graphics.Color
7 |
8 | class ToggleColors(
9 | bg: Color,
10 | off: Color,
11 | on: Color,
12 | ) {
13 | var bg by mutableStateOf(bg)
14 | var off by mutableStateOf(off)
15 | var on by mutableStateOf(on)
16 | }
17 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/color/ToolBarColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.color
2 |
3 | import androidx.compose.foundation.interaction.InteractionSource
4 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
5 | import androidx.compose.foundation.interaction.collectIsPressedAsState
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.State
8 | import androidx.compose.runtime.compositionLocalOf
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.rememberUpdatedState
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.graphics.Color
14 |
15 | class ToolBarColors(
16 | buttonPressed: Color,
17 | buttonHover: Color,
18 | iconSplitBorder: Color,
19 | ) {
20 | var buttonPressed by mutableStateOf(buttonPressed)
21 | var buttonHover by mutableStateOf(buttonHover)
22 | var iconSplitBorder by mutableStateOf(iconSplitBorder)
23 |
24 | fun copy(
25 | buttonPressed: Color = this.buttonPressed,
26 | buttonHover: Color = this.buttonHover,
27 | iconSplitBorder: Color = this.iconSplitBorder,
28 | ): ToolBarColors {
29 | return ToolBarColors(buttonPressed, buttonHover, iconSplitBorder)
30 | }
31 |
32 | @Composable
33 | fun actionIconBgColor(interactionSource: InteractionSource): State {
34 | val pressed by interactionSource.collectIsPressedAsState()
35 | val hover by interactionSource.collectIsHoveredAsState()
36 | val targetValue = when {
37 | pressed -> buttonPressed
38 | hover -> buttonHover
39 | else -> Color.Transparent
40 | }
41 | return rememberUpdatedState(targetValue)
42 | }
43 | }
44 |
45 | fun lightToolBarColors(): ToolBarColors {
46 | return ToolBarColors(
47 | Color(0xFFCFCFCF),
48 | Color(0xFFDFDFDF),
49 | Color(0xFFB3B3B3),
50 | )
51 | }
52 |
53 | fun darkToolBarColors(): ToolBarColors {
54 | return ToolBarColors(
55 | Color(0xFF5C6164),
56 | Color(0xFF4C5052),
57 | Color(0xFF6B6B6B),
58 | )
59 | }
60 |
61 | val LocalToolBarColors = compositionLocalOf { lightToolBarColors() }
62 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ActionButton.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.Indication
5 | import androidx.compose.foundation.IndicationInstance
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.clickable
9 | import androidx.compose.foundation.hoverable
10 | import androidx.compose.foundation.interaction.InteractionSource
11 | import androidx.compose.foundation.interaction.MutableInteractionSource
12 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
13 | import androidx.compose.foundation.interaction.collectIsPressedAsState
14 | import androidx.compose.foundation.layout.Arrangement
15 | import androidx.compose.foundation.layout.Box
16 | import androidx.compose.foundation.layout.PaddingValues
17 | import androidx.compose.foundation.layout.Row
18 | import androidx.compose.foundation.layout.RowScope
19 | import androidx.compose.foundation.layout.defaultMinSize
20 | import androidx.compose.foundation.layout.padding
21 | import androidx.compose.foundation.shape.RoundedCornerShape
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.CompositionLocalProvider
24 | import androidx.compose.runtime.State
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.draw.clip
29 | import androidx.compose.ui.graphics.Color
30 | import androidx.compose.ui.graphics.Shape
31 | import androidx.compose.ui.graphics.drawOutline
32 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
33 | import androidx.compose.ui.unit.dp
34 | import io.kanro.compose.jetbrains.JBTheme
35 |
36 | object ActionButtonIndication : Indication {
37 | private class ActionButtonIndicationInstance(
38 | private val shape: Shape,
39 | private val isHover: State,
40 | private val isPressed: State,
41 | private val hoverColor: Color,
42 | private val pressedColor: Color,
43 | ) : IndicationInstance {
44 | override fun ContentDrawScope.drawIndication() {
45 | when {
46 | isPressed.value -> {
47 | val outline = shape.createOutline(size, layoutDirection, this)
48 | drawOutline(outline, pressedColor)
49 | }
50 |
51 | isHover.value -> {
52 | val outline = shape.createOutline(size, layoutDirection, this)
53 | drawOutline(outline, hoverColor)
54 | }
55 | }
56 | drawContent()
57 | }
58 | }
59 |
60 | @Composable
61 | override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
62 | val shape = remember { RoundedCornerShape(3.dp) }
63 | val isPressed = interactionSource.collectIsPressedAsState()
64 | val isHover = interactionSource.collectIsHoveredAsState()
65 | val hoverColor = JBTheme.toolBarColors.buttonHover
66 | val pressedColor = JBTheme.toolBarColors.buttonPressed
67 | return remember(JBTheme.toolBarColors, interactionSource) {
68 | ActionButtonIndicationInstance(
69 | shape,
70 | isHover,
71 | isPressed,
72 | hoverColor,
73 | pressedColor
74 | )
75 | }
76 | }
77 | }
78 |
79 | object ActionButtonDefaults {
80 | private val ButtonHorizontalPadding = 3.dp
81 | private val ButtonVerticalPadding = 3.dp
82 |
83 | val MinWidth = 22.dp
84 |
85 | val MinHeight = 22.dp
86 |
87 | val IconSize = 16.dp
88 |
89 | val ContentPadding = PaddingValues(
90 | start = ButtonHorizontalPadding,
91 | top = ButtonVerticalPadding,
92 | end = ButtonHorizontalPadding,
93 | bottom = ButtonVerticalPadding
94 | )
95 | }
96 |
97 | @Composable
98 | fun ActionButton(
99 | onClick: () -> Unit,
100 | modifier: Modifier = Modifier,
101 | enabled: Boolean = true,
102 | background: Color = Color.Transparent,
103 | indication: Indication? = ActionButtonIndication,
104 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
105 | shape: Shape = RoundedCornerShape(3.dp),
106 | border: BorderStroke? = null,
107 | contentPadding: PaddingValues = ActionButtonDefaults.ContentPadding,
108 | content: @Composable RowScope.() -> Unit,
109 | ) {
110 | Box(
111 | modifier
112 | .then(if (border != null) Modifier.border(border, shape) else Modifier)
113 | .background(
114 | color = background,
115 | shape = shape
116 | )
117 | .clip(shape)
118 | .clickable(
119 | interactionSource = interactionSource,
120 | indication = indication,
121 | enabled = enabled,
122 | onClick = onClick
123 | )
124 | .hoverable(interactionSource, enabled),
125 | propagateMinConstraints = true
126 | ) {
127 | CompositionLocalProvider(
128 | LocalContentColor provides (if (!enabled) JBTheme.iconColors.disabled else LocalContentColor.current)
129 | ) {
130 | Row(
131 | Modifier
132 | .defaultMinSize(
133 | minWidth = ActionButtonDefaults.MinWidth,
134 | minHeight = ActionButtonDefaults.MinHeight
135 | )
136 | .padding(contentPadding),
137 | horizontalArrangement = Arrangement.Center,
138 | verticalAlignment = Alignment.CenterVertically,
139 | content = content
140 | )
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/CheckBox.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.requiredSize
6 | import androidx.compose.foundation.layout.wrapContentSize
7 | import androidx.compose.foundation.selection.triStateToggleable
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.geometry.CornerRadius
13 | import androidx.compose.ui.geometry.Offset
14 | import androidx.compose.ui.geometry.Size
15 | import androidx.compose.ui.graphics.BlendMode
16 | import androidx.compose.ui.graphics.ColorFilter
17 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
18 | import androidx.compose.ui.semantics.Role
19 | import androidx.compose.ui.state.ToggleableState
20 | import androidx.compose.ui.unit.dp
21 | import io.kanro.compose.jetbrains.JBTheme
22 | import io.kanro.compose.jetbrains.icons.JBIcons
23 | import io.kanro.compose.jetbrains.icons.jbicons.Actions
24 | import io.kanro.compose.jetbrains.icons.jbicons.actions.Checkmark
25 | import io.kanro.compose.jetbrains.icons.jbicons.actions.CheckmarkIndeterminate
26 |
27 | @Composable
28 | fun CheckBox(
29 | checked: Boolean,
30 | onCheckedChange: ((Boolean) -> Unit)?,
31 | modifier: Modifier = Modifier,
32 | enabled: Boolean = true,
33 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
34 | ) {
35 | TriStateCheckbox(
36 | state = ToggleableState(checked),
37 | onClick = if (onCheckedChange != null) {
38 | { onCheckedChange(!checked) }
39 | } else null,
40 | interactionSource = interactionSource,
41 | enabled = enabled,
42 | modifier = modifier
43 | )
44 | }
45 |
46 | @Composable
47 | fun TriStateCheckbox(
48 | state: ToggleableState,
49 | onClick: (() -> Unit)?,
50 | modifier: Modifier = Modifier,
51 | enabled: Boolean = true,
52 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
53 | ) {
54 | val toggleableModifier =
55 | if (onClick != null) {
56 | Modifier.triStateToggleable(
57 | state = state,
58 | onClick = onClick,
59 | enabled = enabled,
60 | role = Role.Checkbox,
61 | interactionSource = interactionSource,
62 | indication = null
63 | )
64 | } else {
65 | Modifier
66 | }
67 |
68 | CheckboxImpl(
69 | enabled = enabled,
70 | value = state,
71 | modifier = modifier
72 | .then(toggleableModifier),
73 | )
74 | }
75 |
76 | @Composable
77 | private fun CheckboxImpl(
78 | enabled: Boolean,
79 | value: ToggleableState,
80 | modifier: Modifier,
81 | ) {
82 | val icon = when (value) {
83 | ToggleableState.On -> rememberVectorPainter(JBIcons.Actions.Checkmark)
84 | ToggleableState.Indeterminate -> rememberVectorPainter(JBIcons.Actions.CheckmarkIndeterminate)
85 | else -> null
86 | }
87 | val iconFilter = if (!enabled) {
88 | ColorFilter.tint(JBTheme.iconColors.disabled, BlendMode.DstIn)
89 | } else null
90 |
91 | val bg = if (enabled) {
92 | when (value) {
93 | ToggleableState.On, ToggleableState.Indeterminate -> JBTheme.checkBoxColors.bgSelected
94 | ToggleableState.Off -> JBTheme.checkBoxColors.bg
95 | }
96 | } else JBTheme.checkBoxColors.bgDisabled
97 |
98 | val border = if (enabled) {
99 | when (value) {
100 | ToggleableState.On, ToggleableState.Indeterminate -> JBTheme.checkBoxColors.borderSelected
101 | ToggleableState.Off -> JBTheme.checkBoxColors.border
102 | }
103 | } else JBTheme.checkBoxColors.borderDisabled
104 |
105 | Canvas(modifier.wrapContentSize(Alignment.Center).requiredSize(14.dp)) {
106 | drawRoundRect(border, cornerRadius = CornerRadius(2.dp.toPx()))
107 | drawRoundRect(
108 | bg,
109 | size = Size(12.dp.toPx(), 12.dp.toPx()),
110 | topLeft = Offset(1.dp.toPx(), 1.dp.toPx()),
111 | cornerRadius = CornerRadius(1.dp.toPx())
112 | )
113 | if (icon != null) {
114 | with(icon) {
115 | 14.dp.toPx()
116 | draw(Size(14.dp.toPx(), 14.dp.toPx()), colorFilter = iconFilter)
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ComboBox.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.focusable
7 | import androidx.compose.foundation.interaction.MutableInteractionSource
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.Spacer
11 | import androidx.compose.foundation.layout.defaultMinSize
12 | import androidx.compose.foundation.layout.fillMaxHeight
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.layout.size
16 | import androidx.compose.foundation.layout.width
17 | import androidx.compose.foundation.shape.RoundedCornerShape
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.mutableStateOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.draw.drawBehind
24 | import androidx.compose.ui.focus.FocusRequester
25 | import androidx.compose.ui.focus.focusRequester
26 | import androidx.compose.ui.geometry.RoundRect
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.graphics.Path
29 | import androidx.compose.ui.graphics.PathFillType
30 | import androidx.compose.ui.graphics.Shape
31 | import androidx.compose.ui.graphics.addOutline
32 | import androidx.compose.ui.unit.Dp
33 | import androidx.compose.ui.unit.DpOffset
34 | import androidx.compose.ui.unit.dp
35 | import io.kanro.compose.jetbrains.JBThemeStyle
36 | import io.kanro.compose.jetbrains.LocalIconTheme
37 | import io.kanro.compose.jetbrains.icons.JBIcons
38 | import io.kanro.compose.jetbrains.icons.jbicons.General
39 | import io.kanro.compose.jetbrains.icons.jbicons.general.ButtonDropTriangle
40 | import io.kanro.compose.jetbrains.icons.jbicons.general.ButtonDropTriangleDark
41 |
42 | @Composable
43 | fun DropdownList(
44 | items: List,
45 | value: T,
46 | onValueChange: ((T) -> Unit)? = null,
47 | modifier: Modifier = Modifier,
48 | enabled: Boolean = true,
49 | valueRender: (T) -> String = { it.toString() },
50 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
51 | style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
52 | ) {
53 | val shape = RoundedCornerShape(3.dp)
54 | val focusRequester = remember { FocusRequester() }
55 | val expanded = remember { mutableStateOf(false) }
56 |
57 | Box(
58 | modifier.border(1.dp, style.borderColor(enabled, false, interactionSource).value, shape).height(24.dp)
59 | .background(style.backgroundColor(enabled, interactionSource).value).focusable(enabled, interactionSource)
60 | .focusRequester(focusRequester)
61 | .clickable(interactionSource = interactionSource, indication = null, enabled = enabled, onClick = {
62 | expanded.value = true
63 | focusRequester.requestFocus()
64 | }).comboBoxIndicator(style.indicatorColor(false, interactionSource).value, shape, 2.dp)
65 | ) {
66 | Row(verticalAlignment = Alignment.CenterVertically) {
67 | Spacer(Modifier.width(6.dp))
68 | Text(
69 | valueRender(value),
70 | Modifier.defaultMinSize(50.dp),
71 | color = style.textColor(enabled, false, interactionSource).value
72 | )
73 | Spacer(Modifier.width(1.dp).fillMaxHeight())
74 | Box(Modifier.size(22.dp), contentAlignment = Alignment.Center) {
75 | val isDarkTheme = LocalIconTheme.current == JBThemeStyle.DARK
76 | Icon(
77 | imageVector = if (isDarkTheme) JBIcons.General.ButtonDropTriangleDark
78 | else JBIcons.General.ButtonDropTriangle
79 | )
80 | }
81 | }
82 |
83 | DropdownMenu(expanded.value, offset = DpOffset(0.dp, 4.dp), onDismissRequest = {
84 | expanded.value = false
85 | }) {
86 | items.forEach {
87 | DropdownMenuItem(
88 | modifier = Modifier.defaultMinSize(79.dp, 21.dp), onClick = {
89 | expanded.value = false
90 | onValueChange?.invoke(it)
91 | }, enabled = enabled
92 | ) {
93 | Text(valueRender(it), Modifier.padding(horizontal = 6.dp))
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | private fun Modifier.comboBoxIndicator(color: Color, shape: Shape, width: Dp): Modifier {
101 | if (color.alpha == 0f) return this
102 | return drawBehind {
103 | val controlOutline = shape.createOutline(size, layoutDirection, this)
104 | val highlightPath = Path().apply {
105 | this.fillType = PathFillType.EvenOdd
106 | addOutline(controlOutline)
107 | val borderRect = controlOutline.bounds.inflate(width.toPx())
108 | addRoundRect(RoundRect(borderRect, 4.dp.toPx(), 4.dp.toPx()))
109 | close()
110 | }
111 | drawPath(highlightPath, color)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentAlpha.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 |
5 | val LocalContentAlpha = compositionLocalOf { 1f }
6 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ContentColor.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.compositionLocalOf
5 | import androidx.compose.ui.graphics.Color
6 | import io.kanro.compose.jetbrains.JBTheme
7 |
8 | val LocalContentColor = compositionLocalOf { Color.Unspecified }
9 |
10 | @Composable
11 | fun JBTheme.contentColorFor(backgroundColor: Color): Color {
12 | return if ((backgroundColor.green + backgroundColor.blue + backgroundColor.red) / 3.0 > 0.6) {
13 | textColors.default
14 | } else {
15 | textColors.white
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/EmbeddedTextField.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.interaction.InteractionSource
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.interaction.collectIsFocusedAsState
6 | import androidx.compose.foundation.text.KeyboardActions
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.State
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.rememberUpdatedState
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.drawBehind
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.graphics.Path
17 | import androidx.compose.ui.graphics.PathFillType
18 | import androidx.compose.ui.graphics.RectangleShape
19 | import androidx.compose.ui.graphics.Shape
20 | import androidx.compose.ui.graphics.addOutline
21 | import androidx.compose.ui.text.TextStyle
22 | import androidx.compose.ui.text.input.VisualTransformation
23 | import androidx.compose.ui.unit.Dp
24 | import androidx.compose.ui.unit.dp
25 |
26 | @Composable
27 | fun EmbeddedTextField(
28 | value: String,
29 | onValueChange: (String) -> Unit,
30 | modifier: Modifier = Modifier,
31 | enabled: Boolean = true,
32 | readOnly: Boolean = false,
33 | textStyle: TextStyle = LocalTextStyle.current,
34 | placeholder: @Composable (() -> Unit)? = null,
35 | leadingIcon: @Composable (() -> Unit)? = null,
36 | trailingIcon: @Composable (() -> Unit)? = null,
37 | isError: Boolean = false,
38 | visualTransformation: VisualTransformation = VisualTransformation.None,
39 | keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
40 | keyboardActions: KeyboardActions = KeyboardActions.Default,
41 | singleLine: Boolean = true,
42 | maxLines: Int = 1,
43 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
44 | shape: Shape = RectangleShape,
45 | style: TextFieldStyle = TextFieldDefaults.textFieldStyle(),
46 | ) {
47 | TextField(
48 | value,
49 | onValueChange,
50 | modifier.embeddedTextFieldIndicator(style.indicatorColor(isError, interactionSource).value, shape, 2.dp),
51 | enabled,
52 | readOnly,
53 | textStyle,
54 | placeholder,
55 | leadingIcon,
56 | trailingIcon,
57 | isError,
58 | visualTransformation,
59 | keyboardOptions,
60 | keyboardActions,
61 | singleLine,
62 | maxLines,
63 | interactionSource,
64 | shape,
65 | InnerStyle(style)
66 | )
67 | }
68 |
69 | private class InnerStyle(val style: TextFieldStyle) : TextFieldStyle {
70 | @Composable
71 | override fun textColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State {
72 | val focused by interactionSource.collectIsFocusedAsState()
73 |
74 | return if (focused) style.textColor(
75 | enabled,
76 | isError,
77 | interactionSource
78 | ) else rememberUpdatedState(Color.Unspecified)
79 | }
80 |
81 | @Composable
82 | override fun backgroundColor(enabled: Boolean, interactionSource: InteractionSource): State {
83 | val focused by interactionSource.collectIsFocusedAsState()
84 |
85 | return if (focused) style.backgroundColor(
86 | enabled,
87 | interactionSource
88 | ) else rememberUpdatedState(Color.Transparent)
89 | }
90 |
91 | @Composable
92 | override fun placeholderColor(enabled: Boolean): State {
93 | return style.placeholderColor(enabled)
94 | }
95 |
96 | @Composable
97 | override fun borderColor(enabled: Boolean, isError: Boolean, interactionSource: InteractionSource): State {
98 | return rememberUpdatedState(Color.Transparent)
99 | }
100 |
101 | @Composable
102 | override fun indicatorColor(isError: Boolean, interactionSource: InteractionSource): State {
103 | return rememberUpdatedState(Color.Transparent)
104 | }
105 |
106 | @Composable
107 | override fun cursorColor(isError: Boolean): State {
108 | return style.cursorColor(isError)
109 | }
110 | }
111 |
112 | private fun Modifier.embeddedTextFieldIndicator(color: Color, shape: Shape, width: Dp): Modifier {
113 | if (color.alpha == 0f) return this
114 | return drawBehind {
115 | val controlOutline = shape.createOutline(size, layoutDirection, this)
116 | val highlightPath = Path().apply {
117 | this.fillType = PathFillType.EvenOdd
118 | addOutline(controlOutline)
119 | addRect(controlOutline.bounds.deflate(width.toPx()))
120 | close()
121 | }
122 | drawPath(highlightPath, color)
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Icon.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.draw.paint
9 | import androidx.compose.ui.geometry.Size
10 | import androidx.compose.ui.graphics.ColorFilter
11 | import androidx.compose.ui.graphics.ImageBitmap
12 | import androidx.compose.ui.graphics.isSpecified
13 | import androidx.compose.ui.graphics.painter.BitmapPainter
14 | import androidx.compose.ui.graphics.painter.Painter
15 | import androidx.compose.ui.graphics.toolingGraphicsLayer
16 | import androidx.compose.ui.graphics.vector.ImageVector
17 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
18 | import androidx.compose.ui.layout.ContentScale
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.semantics.Role
21 | import androidx.compose.ui.semantics.contentDescription
22 | import androidx.compose.ui.semantics.role
23 | import androidx.compose.ui.semantics.semantics
24 | import androidx.compose.ui.unit.dp
25 | import io.kanro.compose.jetbrains.JBThemeStyle
26 | import io.kanro.compose.jetbrains.LocalIconTheme
27 |
28 | @Composable
29 | fun Icon(
30 | resource: String,
31 | contentDescription: String? = null,
32 | modifier: Modifier = Modifier,
33 | colorFilter: ColorFilter? = null,
34 | ) {
35 | Icon(
36 | themedSvgResource(resource, LocalIconTheme.current), contentDescription, modifier,
37 | colorFilter = colorFilter,
38 | )
39 | }
40 |
41 | @Composable
42 | fun Icon(
43 | bitmap: ImageBitmap,
44 | contentDescription: String? = null,
45 | modifier: Modifier = Modifier,
46 | colorFilter: ColorFilter? = null,
47 | ) {
48 | val painter = remember(bitmap) { BitmapPainter(bitmap) }
49 | Icon(
50 | painter = painter,
51 | contentDescription = contentDescription,
52 | modifier = modifier,
53 | colorFilter = colorFilter,
54 | )
55 | }
56 |
57 | @Composable
58 | fun Icon(
59 | imageVector: ImageVector,
60 | contentDescription: String? = null,
61 | modifier: Modifier = Modifier,
62 | colorFilter: ColorFilter? = null,
63 | ) {
64 | Icon(
65 | painter = rememberVectorPainter(imageVector),
66 | contentDescription = contentDescription,
67 | modifier = modifier,
68 | colorFilter = colorFilter,
69 | )
70 | }
71 |
72 | @Composable
73 | fun Icon(
74 | painter: Painter,
75 | contentDescription: String? = null,
76 | modifier: Modifier = Modifier,
77 | colorFilter: ColorFilter? = null,
78 | ) {
79 | val semantics = if (contentDescription != null) {
80 | Modifier.semantics {
81 | this.contentDescription = contentDescription
82 | this.role = Role.Image
83 | }
84 | } else {
85 | Modifier
86 | }
87 | val filter = colorFilter ?: run {
88 | if (LocalContentColor.current.isSpecified) {
89 | ColorFilter.tint(LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
90 | } else {
91 | null
92 | }
93 | }
94 | Box(
95 | modifier.toolingGraphicsLayer().defaultSizeFor(painter)
96 | .paint(
97 | painter,
98 | contentScale = ContentScale.None,
99 | colorFilter = filter
100 | )
101 | .then(semantics)
102 | )
103 | }
104 |
105 | @Composable
106 | fun themedSvgResource(resource: String, theme: JBThemeStyle = LocalIconTheme.current): Painter {
107 | var realResource = resource
108 | if (theme == JBThemeStyle.DARK) {
109 | if (!realResource.endsWith("_dark.svg")) {
110 | val dark = realResource.replace(".svg", "_dark.svg")
111 | if (Thread.currentThread().contextClassLoader.getResource(dark) != null) {
112 | realResource = dark
113 | }
114 | }
115 | } else {
116 | if (realResource.endsWith("_dark.svg")) {
117 | val light = realResource.replace("_dark.svg", ".svg")
118 | if (Thread.currentThread().contextClassLoader.getResource(light) != null) {
119 | realResource = light
120 | }
121 | }
122 | }
123 | return painterResource(realResource)
124 | }
125 |
126 | private fun Modifier.defaultSizeFor(painter: Painter) =
127 | this.then(
128 | if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
129 | DefaultIconSizeModifier
130 | } else {
131 | Modifier
132 | }
133 | )
134 |
135 | private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
136 |
137 | private val DefaultIconSizeModifier = Modifier.size(16.dp)
138 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBToolBar.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.Orientation
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.size
11 | import androidx.compose.foundation.layout.width
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.CompositionLocalProvider
14 | import androidx.compose.runtime.compositionLocalOf
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.unit.dp
19 | import io.kanro.compose.jetbrains.JBTheme
20 |
21 | @Composable
22 | fun JBToolBar(
23 | orientation: Orientation,
24 | arrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(8.dp),
25 | modifier: Modifier = Modifier,
26 | content: @Composable () -> Unit,
27 | ) {
28 | CompositionLocalProvider(
29 | LocalToolBarOrientation provides orientation,
30 | ) {
31 | when (orientation) {
32 | Orientation.Vertical -> JBToolBarColumn(
33 | modifier.width(28.dp),
34 | verticalArrangement = arrangement,
35 | content = content
36 | )
37 |
38 | Orientation.Horizontal -> JBToolBarRow(
39 | modifier.height(28.dp),
40 | horizontalArrangement = arrangement,
41 | content = content
42 | )
43 | }
44 | }
45 | }
46 |
47 | @Composable
48 | private fun JBToolBarColumn(
49 | modifier: Modifier,
50 | verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
51 | content: @Composable () -> Unit,
52 | ) {
53 | Column(
54 | modifier,
55 | verticalArrangement,
56 | Alignment.CenterHorizontally,
57 | ) {
58 | content()
59 | }
60 | }
61 |
62 | @Composable
63 | private fun JBToolBarRow(
64 | modifier: Modifier,
65 | horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp),
66 | content: @Composable () -> Unit,
67 | ) {
68 | Row(
69 | modifier,
70 | horizontalArrangement,
71 | Alignment.CenterVertically,
72 | ) {
73 | content()
74 | }
75 | }
76 |
77 | val LocalToolBarOrientation = compositionLocalOf { Orientation.Horizontal }
78 |
79 | @Composable
80 | fun ToolBarSeparator(modifier: Modifier = Modifier, color: Color = JBTheme.toolBarColors.iconSplitBorder) {
81 | val orientation = LocalToolBarOrientation.current
82 | Spacer(
83 | modifier = modifier.run {
84 | when (orientation) {
85 | Orientation.Vertical -> size(20.dp, 1.dp)
86 | Orientation.Horizontal -> size(1.dp, 20.dp)
87 | }
88 | }.background(color)
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JBTree.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.combinedClickable
7 | import androidx.compose.foundation.hoverable
8 | import androidx.compose.foundation.interaction.MutableInteractionSource
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.Column
11 | import androidx.compose.foundation.layout.Row
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.CompositionLocalProvider
17 | import androidx.compose.runtime.ProvidableCompositionLocal
18 | import androidx.compose.runtime.compositionLocalOf
19 | import androidx.compose.runtime.remember
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.graphicsLayer
23 | import androidx.compose.ui.unit.dp
24 | import io.kanro.compose.jetbrains.JBTheme
25 | import io.kanro.compose.jetbrains.JBThemeStyle
26 | import io.kanro.compose.jetbrains.LocalIconTheme
27 | import io.kanro.compose.jetbrains.icons.JBIcons
28 | import io.kanro.compose.jetbrains.icons.jbicons.Actions
29 | import io.kanro.compose.jetbrains.icons.jbicons.actions.ArrowExpand
30 | import io.kanro.compose.jetbrains.icons.jbicons.actions.ArrowExpandDark
31 |
32 | @Composable
33 | fun JBTreeItem(
34 | modifier: Modifier = Modifier,
35 | selected: Boolean,
36 | onClick: () -> Unit,
37 | content: @Composable () -> Unit,
38 | ) {
39 | JBTreeBasicItem(modifier, selected, onClick) {
40 | Box(Modifier.padding(start = 16.dp)) {
41 | content()
42 | }
43 | }
44 | }
45 |
46 | @Composable
47 | fun JBTreeItem(
48 | modifier: Modifier = Modifier,
49 | selected: Boolean,
50 | onClick: () -> Unit,
51 | expanded: Boolean,
52 | expanding: (Boolean) -> Unit,
53 | content: @Composable () -> Unit,
54 | children: @Composable () -> Unit,
55 | ) {
56 | Column(modifier) {
57 | JBTreeBasicItem(
58 | modifier, selected, onClick = onClick,
59 | onDoubleClick = {
60 | expanding(!expanded)
61 | onClick()
62 | }
63 | ) {
64 | Row {
65 | val isDarkTheme = LocalIconTheme.current == JBThemeStyle.DARK
66 | Icon(
67 | imageVector = if (isDarkTheme) JBIcons.Actions.ArrowExpandDark else JBIcons.Actions.ArrowExpand,
68 | modifier = Modifier.size(16.dp).clickable(
69 | interactionSource = remember { MutableInteractionSource() },
70 | indication = null
71 | ) {
72 | expanding(!expanded)
73 | onClick()
74 | }.graphicsLayer(rotationZ = if (expanded) 90f else 0f).align(Alignment.CenterVertically)
75 | )
76 | Box {
77 | content()
78 | }
79 | }
80 | }
81 | if (expanded) {
82 | CompositionLocalProvider(
83 | LocalTreeLevel provides LocalTreeLevel.current + 1,
84 | content = children
85 | )
86 | }
87 | }
88 | }
89 |
90 | @Composable
91 | @OptIn(ExperimentalFoundationApi::class)
92 | internal fun JBTreeBasicItem(
93 | modifier: Modifier = Modifier,
94 | selected: Boolean = false,
95 | onClick: () -> Unit,
96 | onDoubleClick: (() -> Unit)? = null,
97 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
98 | content: @Composable () -> Unit,
99 | ) {
100 | val padding = 7 + (LocalTreeLevel.current - 1) * 18
101 | Box(
102 | Modifier.combinedClickable(
103 | interactionSource,
104 | indication = ListItemHoverIndication,
105 | onDoubleClick = onDoubleClick,
106 | onClick = onClick
107 | ).then(modifier)
108 | .height(20.dp)
109 | .run {
110 | if (selected) {
111 | background(color = JBTheme.selectionColors.active)
112 | } else {
113 | this
114 | }
115 | }.hoverable(interactionSource),
116 | ) {
117 | SelectionScope(selected) {
118 | Box(Modifier.padding(start = padding.dp)) {
119 | content()
120 | }
121 | }
122 | }
123 | }
124 |
125 | @Composable
126 | fun JBTreeList(
127 | modifier: Modifier = Modifier,
128 | content: @Composable () -> Unit,
129 | ) {
130 | Column(modifier) {
131 | content()
132 | }
133 | }
134 |
135 | val LocalTreeLevel: ProvidableCompositionLocal = compositionLocalOf {
136 | 1
137 | }
138 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/JPanel.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.BoxScope
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.draw.drawWithCache
10 | import androidx.compose.ui.geometry.Offset
11 | import androidx.compose.ui.geometry.Rect
12 | import androidx.compose.ui.geometry.Size
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.unit.Dp
15 | import androidx.compose.ui.unit.dp
16 | import io.kanro.compose.jetbrains.JBTheme
17 |
18 | @Composable
19 | fun JPanel(
20 | modifier: Modifier = Modifier,
21 | content: @Composable BoxScope.() -> Unit,
22 | ) {
23 | Box(modifier.background(JBTheme.panelColors.bgDialog)) {
24 | content()
25 | }
26 | }
27 |
28 | @Composable
29 | fun JPanelBorder(modifier: Modifier = Modifier) {
30 | Spacer(modifier.background(JBTheme.panelColors.border))
31 | }
32 |
33 | fun Modifier.jBorder(all: Dp = 0.dp, color: Color): Modifier {
34 | return jBorder(all, all, all, all, color)
35 | }
36 |
37 | fun Modifier.jBorder(
38 | horizontal: Dp = 0.dp,
39 | vertical: Dp = 0.dp,
40 | color: Color,
41 | ): Modifier {
42 | return jBorder(horizontal, horizontal, vertical, vertical, color)
43 | }
44 |
45 | fun Modifier.jBorder(
46 | start: Dp = 0.dp,
47 | end: Dp = 0.dp,
48 | top: Dp = 0.dp,
49 | bottom: Dp = 0.dp,
50 | color: Color,
51 | ): Modifier {
52 | return drawWithCache {
53 | onDrawWithContent {
54 | drawContent()
55 | var rect = Rect(Offset.Zero, size)
56 |
57 | if (start.roundToPx() > 0) {
58 | drawRect(color, rect.topLeft, Size(start.toPx(), rect.height))
59 | rect = Rect(rect.left + start.roundToPx(), rect.top, rect.right, rect.bottom)
60 | }
61 |
62 | if (end.roundToPx() > 0) {
63 | drawRect(color, Offset(rect.right - end.toPx(), rect.top), Size(end.toPx(), rect.height))
64 | rect = Rect(rect.left, rect.top, rect.right - end.roundToPx(), rect.bottom)
65 | }
66 |
67 | if (top.roundToPx() > 0) {
68 | drawRect(color, rect.topLeft, Size(rect.width, top.toPx()))
69 | rect = Rect(rect.left, rect.top + top.roundToPx(), rect.right, rect.bottom)
70 | }
71 |
72 | if (bottom.roundToPx() > 0) {
73 | drawRect(color, Offset(rect.left, rect.bottom - bottom.toPx()), Size(rect.width, bottom.toPx()))
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ListView.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.IndicationInstance
5 | import androidx.compose.foundation.interaction.InteractionSource
6 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.State
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
12 | import io.kanro.compose.jetbrains.JBTheme
13 |
14 | object ListItemHoverIndication : Indication {
15 | private class HoverIndicationInstance(
16 | private val isHover: State,
17 | private val hoverColor: Color,
18 | ) : IndicationInstance {
19 | override fun ContentDrawScope.drawIndication() {
20 | if (isHover.value) {
21 | drawRect(hoverColor, size = size)
22 | }
23 | drawContent()
24 | }
25 | }
26 |
27 | @Composable
28 | override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
29 | val isHover = interactionSource.collectIsHoveredAsState()
30 | val hoverColor = JBTheme.selectionColors.hover
31 |
32 | return remember(JBTheme.selectionColors, interactionSource) {
33 | HoverIndicationInstance(
34 | isHover, hoverColor
35 | )
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/ProgressBar.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.animation.core.animateFloat
4 | import androidx.compose.animation.core.infiniteRepeatable
5 | import androidx.compose.animation.core.keyframes
6 | import androidx.compose.animation.core.rememberInfiniteTransition
7 | import androidx.compose.foundation.Canvas
8 | import androidx.compose.foundation.focusable
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.progressSemantics
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.geometry.Offset
15 | import androidx.compose.ui.graphics.Brush
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.StrokeCap
18 | import androidx.compose.ui.graphics.TileMode
19 | import androidx.compose.ui.unit.dp
20 | import io.kanro.compose.jetbrains.JBTheme
21 |
22 | @Composable
23 | fun ProgressBar(
24 | progress: Float,
25 | modifier: Modifier = Modifier,
26 | ) {
27 | val bgColor = JBTheme.progressColors.bg
28 | val progressColor = JBTheme.progressColors.progress
29 | Canvas(
30 | modifier
31 | .progressSemantics(progress)
32 | .size(200.dp, 4.dp)
33 | .focusable()
34 | ) {
35 | val strokeWidth = size.height
36 | val length = size.width
37 |
38 | drawLine(
39 | bgColor,
40 | Offset(0f, strokeWidth / 2f),
41 | Offset(length, strokeWidth / 2f),
42 | strokeWidth,
43 | cap = StrokeCap.Round
44 | )
45 | drawLine(
46 | progressColor,
47 | Offset(0f, strokeWidth / 2f),
48 | Offset(length * progress, strokeWidth / 2f),
49 | strokeWidth,
50 | cap = StrokeCap.Round
51 | )
52 | }
53 | }
54 |
55 | @Composable
56 | fun ProgressBar(
57 | modifier: Modifier = Modifier,
58 | ) {
59 | val transition = rememberInfiniteTransition()
60 | val currentOffset by transition.animateFloat(
61 | 0f,
62 | 1f,
63 | infiniteRepeatable(
64 | animation = keyframes {
65 | durationMillis = 1000
66 | }
67 | )
68 | )
69 | val progressColor = JBTheme.progressColors.progress
70 |
71 | Canvas(
72 | modifier
73 | .progressSemantics()
74 | .size(200.dp, 4.dp)
75 | .focusable()
76 | ) {
77 | val strokeWidth = size.height
78 | val length = size.width
79 | val offset = currentOffset * 80f
80 | val brush = Brush.linearGradient(
81 | listOf(
82 | Color(0x00FFFFFF), Color(0x7FFFFFFF), Color(0x00FFFFFF)
83 | ),
84 | start = Offset(offset, 0f),
85 | end = Offset(offset + 80f, 0f),
86 | tileMode = TileMode.Repeated
87 | )
88 | drawLine(
89 | progressColor,
90 | Offset(0f, strokeWidth / 2f),
91 | Offset(length, strokeWidth / 2f),
92 | strokeWidth,
93 | cap = StrokeCap.Round
94 | )
95 | drawLine(
96 | brush,
97 | Offset(0f, strokeWidth / 2f),
98 | Offset(length, strokeWidth / 2f),
99 | strokeWidth,
100 | cap = StrokeCap.Round
101 | )
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Selection.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.RowScope
8 | import androidx.compose.foundation.selection.selectable
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.ProvidedValue
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.drawWithContent
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.semantics.Role
18 | import io.kanro.compose.jetbrains.JBTheme
19 | import io.kanro.compose.jetbrains.JBThemeStyle
20 | import io.kanro.compose.jetbrains.LocalIconTheme
21 | import io.kanro.compose.jetbrains.LocalSelectionScope
22 | import io.kanro.compose.jetbrains.color.LocalTextColors
23 |
24 | val emptySelectionScope = emptyArray>()
25 |
26 | val lightSelectionScope: @Composable () -> Array> = {
27 | arrayOf(
28 | LocalIconTheme provides JBThemeStyle.DARK,
29 | LocalTextColors provides JBTheme.textColors.copy(
30 | infoInput = Color.White
31 | ),
32 | LocalContentColor provides Color.White,
33 | LocalContentAlpha provides 1.0f,
34 | LocalContextMenuRepresentation provides JBContextMenuRepresentation(
35 | JBTheme.panelColors.bgContent, JBTheme.panelColors.border
36 | )
37 | )
38 | }
39 |
40 | val darkSelectionScope: @Composable () -> Array> = {
41 | arrayOf(
42 | LocalIconTheme provides JBThemeStyle.DARK,
43 | LocalTextColors provides JBTheme.textColors.copy(
44 | infoInput = Color.White
45 | ),
46 | LocalContentColor provides Color.White,
47 | LocalContentAlpha provides 1.0f,
48 | LocalContextMenuRepresentation provides JBContextMenuRepresentation(
49 | JBTheme.panelColors.bgContent, JBTheme.panelColors.border
50 | )
51 | )
52 | }
53 |
54 | @Composable
55 | fun SelectionScope(selected: Boolean, block: @Composable () -> Unit) {
56 | CompositionLocalProvider(* if (selected) LocalSelectionScope.current() else emptySelectionScope) {
57 | block()
58 | }
59 | }
60 |
61 | @Composable
62 | fun SelectionRow(
63 | selected: Boolean,
64 | modifier: Modifier = Modifier,
65 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
66 | verticalAlignment: Alignment.Vertical = Alignment.Top,
67 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
68 | role: Role? = null,
69 | onClick: () -> Unit,
70 | content: @Composable RowScope.() -> Unit,
71 | ) {
72 | SelectionScope(selected) {
73 | val selectedColor = JBTheme.selectionColors.active
74 | Row(
75 | modifier = modifier.background(color = JBTheme.panelColors.bgContent).selectable(
76 | selected = selected,
77 | interactionSource = interactionSource,
78 | indication = ListItemHoverIndication,
79 | onClick = onClick,
80 | role = role
81 | ).drawWithContent {
82 | if (selected) {
83 | drawRect(selectedColor, size = size)
84 | }
85 | drawContent()
86 | },
87 | horizontalArrangement = horizontalArrangement, verticalAlignment = verticalAlignment, content = content
88 | )
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Tab.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.IndicationInstance
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.hoverable
7 | import androidx.compose.foundation.interaction.InteractionSource
8 | import androidx.compose.foundation.interaction.MutableInteractionSource
9 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.BoxScope
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.selection.selectable
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.State
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.draw.drawWithContent
20 | import androidx.compose.ui.geometry.Offset
21 | import androidx.compose.ui.geometry.Size
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
24 | import androidx.compose.ui.semantics.Role
25 | import androidx.compose.ui.unit.dp
26 | import io.kanro.compose.jetbrains.JBTheme
27 |
28 | object TabIndication : Indication {
29 | private class TabIndicationInstance(
30 | private val isHover: State,
31 | private val hoverColor: Color,
32 | ) : IndicationInstance {
33 | override fun ContentDrawScope.drawIndication() {
34 | if (isHover.value) {
35 | drawRect(hoverColor, Offset.Zero, size)
36 | }
37 | drawContent()
38 | }
39 | }
40 |
41 | @Composable
42 | override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
43 | val isHover = interactionSource.collectIsHoveredAsState()
44 | val hoverColor = JBTheme.tabColors.hover
45 |
46 | return remember(JBTheme.tabColors, interactionSource) {
47 | TabIndicationInstance(
48 | isHover,
49 | hoverColor,
50 | )
51 | }
52 | }
53 | }
54 |
55 | @Composable
56 | fun Tab(
57 | selected: Boolean,
58 | onClick: () -> Unit,
59 | modifier: Modifier = Modifier,
60 | enabled: Boolean = true,
61 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
62 | content: @Composable BoxScope.() -> Unit,
63 | ) {
64 | val selectionColor = JBTheme.tabColors.selection
65 | Box(
66 | modifier
67 | .hoverable(interactionSource)
68 | .height(28.dp)
69 | .run {
70 | if (selected) {
71 | background(JBTheme.tabColors.bgSelected)
72 | } else {
73 | this
74 | }
75 | }
76 | .drawWithContent {
77 | drawContent()
78 | if (selected) {
79 | val height = 3.dp.toPx()
80 | drawRect(
81 | selectionColor,
82 | Offset(0f, size.height - height),
83 | Size(size.width, height)
84 | )
85 | }
86 | }.selectable(
87 | selected = selected,
88 | onClick = onClick,
89 | enabled = enabled,
90 | role = Role.Tab,
91 | interactionSource = interactionSource,
92 | indication = TabIndication
93 | ),
94 | propagateMinConstraints = true,
95 | contentAlignment = Alignment.Center,
96 | content = content
97 | )
98 | }
99 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/control/Text.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.control
2 |
3 | import androidx.compose.foundation.text.BasicText
4 | import androidx.compose.foundation.text.InlineTextContent
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.CompositionLocalProvider
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.compose.runtime.structuralEqualityPolicy
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.takeOrElse
12 | import androidx.compose.ui.text.AnnotatedString
13 | import androidx.compose.ui.text.TextLayoutResult
14 | import androidx.compose.ui.text.TextStyle
15 | import androidx.compose.ui.text.font.FontFamily
16 | import androidx.compose.ui.text.font.FontStyle
17 | import androidx.compose.ui.text.font.FontWeight
18 | import androidx.compose.ui.text.style.TextAlign
19 | import androidx.compose.ui.text.style.TextDecoration
20 | import androidx.compose.ui.text.style.TextOverflow
21 | import androidx.compose.ui.unit.TextUnit
22 | import io.kanro.compose.jetbrains.color.LocalTextColors
23 | import io.kanro.compose.jetbrains.color.TextColors
24 |
25 | @Composable
26 | fun Text(
27 | text: String,
28 | modifier: Modifier = Modifier,
29 | color: Color = Color.Unspecified,
30 | fontSize: TextUnit = TextUnit.Unspecified,
31 | fontStyle: FontStyle? = null,
32 | fontWeight: FontWeight? = null,
33 | fontFamily: FontFamily? = null,
34 | letterSpacing: TextUnit = TextUnit.Unspecified,
35 | textDecoration: TextDecoration? = null,
36 | textAlign: TextAlign? = null,
37 | lineHeight: TextUnit = TextUnit.Unspecified,
38 | overflow: TextOverflow = TextOverflow.Clip,
39 | softWrap: Boolean = true,
40 | maxLines: Int = Int.MAX_VALUE,
41 | onTextLayout: (TextLayoutResult) -> Unit = {},
42 | style: TextStyle = LocalTextStyle.current,
43 | textColors: TextColors = LocalTextColors.current,
44 | ) {
45 | Text(
46 | AnnotatedString(text),
47 | modifier,
48 | color,
49 | fontSize,
50 | fontStyle,
51 | fontWeight,
52 | fontFamily,
53 | letterSpacing,
54 | textDecoration,
55 | textAlign,
56 | lineHeight,
57 | overflow,
58 | softWrap,
59 | maxLines,
60 | emptyMap(),
61 | onTextLayout,
62 | style,
63 | textColors,
64 | )
65 | }
66 |
67 | @Composable
68 | fun Text(
69 | text: AnnotatedString,
70 | modifier: Modifier = Modifier,
71 | color: Color = Color.Unspecified,
72 | fontSize: TextUnit = TextUnit.Unspecified,
73 | fontStyle: FontStyle? = null,
74 | fontWeight: FontWeight? = null,
75 | fontFamily: FontFamily? = null,
76 | letterSpacing: TextUnit = TextUnit.Unspecified,
77 | textDecoration: TextDecoration? = null,
78 | textAlign: TextAlign? = null,
79 | lineHeight: TextUnit = TextUnit.Unspecified,
80 | overflow: TextOverflow = TextOverflow.Clip,
81 | softWrap: Boolean = true,
82 | maxLines: Int = Int.MAX_VALUE,
83 | inlineContent: Map = mapOf(),
84 | onTextLayout: (TextLayoutResult) -> Unit = {},
85 | style: TextStyle = LocalTextStyle.current,
86 | textColors: TextColors = LocalTextColors.current,
87 | ) {
88 | val textColor = color.takeOrElse {
89 | style.color.takeOrElse {
90 | LocalContentColor.current.takeOrElse {
91 | textColors.default
92 | }.copy(alpha = LocalContentAlpha.current)
93 | }
94 | }
95 | val mergedStyle = style.merge(
96 | TextStyle(
97 | color = textColor,
98 | fontSize = fontSize,
99 | fontWeight = fontWeight,
100 | textAlign = textAlign,
101 | lineHeight = lineHeight,
102 | fontFamily = fontFamily,
103 | textDecoration = textDecoration,
104 | fontStyle = fontStyle,
105 | letterSpacing = letterSpacing,
106 | ),
107 | )
108 | BasicText(
109 | text = text,
110 | modifier = modifier,
111 | style = mergedStyle,
112 | onTextLayout = onTextLayout,
113 | overflow = overflow,
114 | softWrap = softWrap,
115 | maxLines = maxLines,
116 | minLines = 1,
117 | inlineContent = inlineContent,
118 | )
119 | }
120 |
121 | val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
122 |
123 | @Composable
124 | fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
125 | val mergedStyle = LocalTextStyle.current.merge(value)
126 | CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
127 | }
128 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/__JBIcons.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons
2 |
3 | internal object JBIcons
4 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__Actions.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons
2 |
3 | import io.kanro.compose.jetbrains.icons.JBIcons
4 |
5 | internal object ActionsGroup
6 |
7 | internal val JBIcons.Actions: ActionsGroup
8 | get() = ActionsGroup
9 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/__General.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons
2 |
3 | import io.kanro.compose.jetbrains.icons.JBIcons
4 |
5 | internal object GeneralGroup
6 |
7 | internal val JBIcons.General: GeneralGroup
8 | get() = GeneralGroup
9 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpand.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.actions
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
13 |
14 | internal val ActionsGroup.ArrowExpand: ImageVector
15 | get() {
16 | if (_arrowExpand != null) {
17 | return _arrowExpand!!
18 | }
19 | _arrowExpand = Builder(
20 | name = "ArrowExpand", defaultWidth = 16.0.dp,
21 | defaultHeight =
22 | 16.0.dp,
23 | viewportWidth = 16.0f, viewportHeight = 16.0f
24 | ).apply {
25 | path(
26 | fill = SolidColor(Color(0x00000000)), stroke = SolidColor(Color(0xFF6E6E6E)),
27 | strokeLineWidth = 2.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
28 | strokeLineMiter = 4.0f, pathFillType = NonZero
29 | ) {
30 | moveTo(6.0f, 13.0f)
31 | lineTo(11.0f, 8.0f)
32 | lineTo(6.0f, 3.0f)
33 | }
34 | }
35 | .build()
36 | return _arrowExpand!!
37 | }
38 |
39 | private var _arrowExpand: ImageVector? = null
40 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/ArrowExpandDark.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.actions
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
13 |
14 | internal val ActionsGroup.ArrowExpandDark: ImageVector
15 | get() {
16 | if (_arrowExpandDark != null) {
17 | return _arrowExpandDark!!
18 | }
19 | _arrowExpandDark = Builder(
20 | name = "ArrowexpandDark", defaultWidth = 16.0.dp,
21 | defaultHeight =
22 | 16.0.dp,
23 | viewportWidth = 16.0f, viewportHeight = 16.0f
24 | ).apply {
25 | path(
26 | fill = SolidColor(Color(0x00000000)), stroke = SolidColor(Color(0xFFAFB1B3)),
27 | strokeLineWidth = 2.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
28 | strokeLineMiter = 4.0f, pathFillType = NonZero
29 | ) {
30 | moveTo(6.0f, 13.0f)
31 | lineTo(11.0f, 8.0f)
32 | lineTo(6.0f, 3.0f)
33 | }
34 | }
35 | .build()
36 | return _arrowExpandDark!!
37 | }
38 |
39 | private var _arrowExpandDark: ImageVector? = null
40 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Checkmark.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.actions
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
13 |
14 | internal val ActionsGroup.Checkmark: ImageVector
15 | get() {
16 | if (_checkmark != null) {
17 | return _checkmark!!
18 | }
19 | _checkmark = Builder(
20 | name = "Checkmark", defaultWidth = 14.0.dp, defaultHeight = 14.0.dp,
21 | viewportWidth = 14.0f, viewportHeight = 14.0f
22 | ).apply {
23 | path(
24 | fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f,
25 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
26 | pathFillType = EvenOdd
27 | ) {
28 | moveTo(5.625f, 8.4267f)
29 | lineTo(9.5566f, 2.9336f)
30 | curveTo(9.5566f, 2.9336f, 10.1737f, 2.3242f, 10.8612f, 2.8242f)
31 | curveTo(11.4433f, 3.3867f, 10.998f, 4.0938f, 10.998f, 4.0938f)
32 | lineTo(6.3183f, 10.6445f)
33 | curveTo(6.3183f, 10.6445f, 5.9941f, 11.0f, 5.5839f, 11.0f)
34 | curveTo(5.1737f, 11.0f, 4.873f, 10.6445f, 4.873f, 10.6445f)
35 | lineTo(2.9394f, 7.7461f)
36 | curveTo(2.9394f, 7.7461f, 2.5683f, 6.9805f, 3.2558f, 6.4609f)
37 | curveTo(4.0605f, 6.0781f, 4.5605f, 6.8394f, 4.5605f, 6.8394f)
38 | lineTo(5.625f, 8.4267f)
39 | close()
40 | }
41 | }
42 | .build()
43 | return _checkmark!!
44 | }
45 |
46 | private var _checkmark: ImageVector? = null
47 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/CheckmarkIndeterminate.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.actions
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
13 |
14 | internal val ActionsGroup.CheckmarkIndeterminate: ImageVector
15 | get() {
16 | if (_checkmarkIndeterminate != null) {
17 | return _checkmarkIndeterminate!!
18 | }
19 | _checkmarkIndeterminate = Builder(
20 | name = "CheckmarkIndeterminate", defaultWidth = 14.0.dp,
21 | defaultHeight = 14.0.dp, viewportWidth = 14.0f, viewportHeight = 14.0f
22 | ).apply {
23 | path(
24 | fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f,
25 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
26 | pathFillType = NonZero
27 | ) {
28 | moveTo(3.7402f, 5.73f)
29 | lineTo(10.1402f, 5.73f)
30 | arcTo(1.0f, 1.0f, 0.0f, false, true, 11.1402f, 6.73f)
31 | lineTo(11.1402f, 7.23f)
32 | arcTo(1.0f, 1.0f, 0.0f, false, true, 10.1402f, 8.23f)
33 | lineTo(3.7402f, 8.23f)
34 | arcTo(1.0f, 1.0f, 0.0f, false, true, 2.7402f, 7.23f)
35 | lineTo(2.7402f, 6.73f)
36 | arcTo(1.0f, 1.0f, 0.0f, false, true, 3.7402f, 5.73f)
37 | close()
38 | }
39 | }
40 | .build()
41 | return _checkmarkIndeterminate!!
42 | }
43 |
44 | private var _checkmarkIndeterminate: ImageVector? = null
45 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/actions/Close.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.actions
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.ActionsGroup
13 |
14 | internal val ActionsGroup.Close: ImageVector
15 | get() {
16 | if (_close != null) {
17 | return _close!!
18 | }
19 | _close = Builder(
20 | name = "Close", defaultWidth = 16.0.dp, defaultHeight = 16.0.dp,
21 | viewportWidth = 16.0f, viewportHeight = 16.0f
22 | ).apply {
23 | path(
24 | fill = SolidColor(Color(0xFF7F8B91)), stroke = null, fillAlpha = 0.5f,
25 | strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
26 | strokeLineMiter = 4.0f, pathFillType = EvenOdd
27 | ) {
28 | moveTo(7.9949f, 8.7051f)
29 | lineTo(4.8541f, 11.8541f)
30 | lineTo(4.147f, 11.147f)
31 | lineTo(7.2949f, 8.0051f)
32 | lineTo(4.147f, 4.8571f)
33 | lineTo(4.8541f, 4.15f)
34 | lineTo(8.002f, 7.298f)
35 | lineTo(11.144f, 4.15f)
36 | lineTo(11.8511f, 4.8571f)
37 | lineTo(8.702f, 7.998f)
38 | lineTo(11.8511f, 11.147f)
39 | lineTo(11.144f, 11.8541f)
40 | lineTo(7.9949f, 8.7051f)
41 | close()
42 | }
43 | }
44 | .build()
45 | return _close!!
46 | }
47 |
48 | private var _close: ImageVector? = null
49 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangle.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.general
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.GeneralGroup
13 |
14 | internal val GeneralGroup.ButtonDropTriangle: ImageVector
15 | get() {
16 | if (_buttonDropTriangle != null) {
17 | return _buttonDropTriangle!!
18 | }
19 | _buttonDropTriangle = Builder(
20 | name = "ButtonDropTriangle", defaultWidth = 8.0.dp,
21 | defaultHeight = 4.0.dp, viewportWidth = 8.0f, viewportHeight = 4.0f
22 | ).apply {
23 | path(
24 | fill = SolidColor(Color(0xFF6E6E6E)), stroke = null, strokeLineWidth = 0.0f,
25 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
26 | pathFillType = EvenOdd
27 | ) {
28 | moveTo(4.0f, 4.0f)
29 | lineToRelative(4.0f, -4.0f)
30 | lineToRelative(-8.0f, -0.0f)
31 | close()
32 | }
33 | }
34 | .build()
35 | return _buttonDropTriangle!!
36 | }
37 |
38 | private var _buttonDropTriangle: ImageVector? = null
39 |
--------------------------------------------------------------------------------
/classic/src/main/kotlin/io/kanro/compose/jetbrains/icons/jbicons/general/ButtonDropTriangleDark.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.icons.jbicons.general
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder
10 | import androidx.compose.ui.graphics.vector.path
11 | import androidx.compose.ui.unit.dp
12 | import io.kanro.compose.jetbrains.icons.jbicons.GeneralGroup
13 |
14 | internal val GeneralGroup.ButtonDropTriangleDark: ImageVector
15 | get() {
16 | if (_buttonDropTriangleDark != null) {
17 | return _buttonDropTriangleDark!!
18 | }
19 | _buttonDropTriangleDark = Builder(
20 | name = "ButtonDropTriangleDark", defaultWidth = 8.0.dp,
21 | defaultHeight = 4.0.dp, viewportWidth = 8.0f, viewportHeight = 4.0f
22 | ).apply {
23 | path(
24 | fill = SolidColor(Color(0xFFAFB1B3)), stroke = null, strokeLineWidth = 0.0f,
25 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
26 | pathFillType = EvenOdd
27 | ) {
28 | moveTo(4.0f, 4.0f)
29 | lineToRelative(4.0f, -4.0f)
30 | lineToRelative(-8.0f, -0.0f)
31 | close()
32 | }
33 | }
34 | .build()
35 | return _buttonDropTriangleDark!!
36 | }
37 |
38 | private var _buttonDropTriangleDark: ImageVector? = null
39 |
--------------------------------------------------------------------------------
/docs/gradle-jvm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/docs/gradle-jvm.png
--------------------------------------------------------------------------------
/docs/project-sdk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/docs/project-sdk.png
--------------------------------------------------------------------------------
/docs/screenshot-expui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/docs/screenshot-expui.png
--------------------------------------------------------------------------------
/docs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/docs/screenshot.png
--------------------------------------------------------------------------------
/expui-gallery/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | id("org.jetbrains.compose")
4 | }
5 |
6 | dependencies {
7 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
8 | implementation(project(":compose-jetbrains-expui-theme"))
9 | implementation(compose.desktop.currentOs) {
10 | exclude("org.jetbrains.compose.material")
11 | }
12 | implementation(compose.uiTooling)
13 | }
14 |
15 | compose.desktop {
16 | application {
17 | mainClass = "MainKt"
18 | nativeDistributions {
19 | packageName = "JetBrains ExpUI Gallery"
20 | packageVersion = project.version.toString()
21 | copyright = "Beijing Muke Technology Co., Ltd."
22 | modules("jdk.unsupported")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/darkTheme.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/darkTheme_dark.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/generic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/generic_dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/github.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/github_dark.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/kotlin.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/lightTheme.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/lightTheme_dark.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/settings.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/settings_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/expui-gallery/src/main/resources/icons/text_dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/expui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | id("com.netflix.nebula.maven-publish")
4 | id("com.netflix.nebula.source-jar")
5 | id("com.bybutter.sisyphus.project")
6 | id("org.jetbrains.compose")
7 | `java-library`
8 | }
9 |
10 | description = "JetBrains ExpUI Kit for Compose Desktop"
11 |
12 | dependencies {
13 | implementation(kotlin("stdlib"))
14 | implementation(compose.desktop.common) {
15 | exclude("org.jetbrains.compose.material")
16 | }
17 | }
18 |
19 | tasks.withType() {
20 | kotlinOptions.jvmTarget = "17"
21 | }
22 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/DesktopPlatform.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui
2 |
3 | enum class DesktopPlatform {
4 | Linux,
5 | Windows,
6 | MacOS,
7 | Unknown;
8 |
9 | companion object {
10 | val Current: DesktopPlatform by lazy {
11 | val name = System.getProperty("os.name")
12 | when {
13 | name?.startsWith("Linux") == true -> Linux
14 | name?.startsWith("Win") == true -> Windows
15 | name == "Mac OS X" -> MacOS
16 | else -> Unknown
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ActionButton.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.interaction.MutableInteractionSource
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.BoxScope
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.compositionLocalOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Shape
15 | import androidx.compose.ui.semantics.Role
16 | import androidx.compose.ui.unit.dp
17 | import io.kanro.compose.jetbrains.expui.style.AreaColors
18 | import io.kanro.compose.jetbrains.expui.style.AreaProvider
19 | import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
20 | import io.kanro.compose.jetbrains.expui.style.HoverAreaProvider
21 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
22 | import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
23 | import io.kanro.compose.jetbrains.expui.style.LocalHoverAreaColors
24 | import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
25 | import io.kanro.compose.jetbrains.expui.style.LocalPressedAreaColors
26 | import io.kanro.compose.jetbrains.expui.style.PressedAreaProvider
27 | import io.kanro.compose.jetbrains.expui.style.areaBackground
28 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
29 |
30 | class ActionButtonColors(
31 | override val normalAreaColors: AreaColors,
32 | override val hoverAreaColors: AreaColors,
33 | override val pressedAreaColors: AreaColors,
34 | override val disabledAreaColors: AreaColors,
35 | ) : AreaProvider, HoverAreaProvider, PressedAreaProvider, DisabledAreaProvider {
36 | @Composable
37 | fun provideArea(enabled: Boolean, content: @Composable () -> Unit) {
38 | CompositionLocalProvider(
39 | LocalAreaColors provides if (enabled) normalAreaColors else disabledAreaColors,
40 | LocalNormalAreaColors provides normalAreaColors,
41 | LocalDisabledAreaColors provides disabledAreaColors,
42 | LocalHoverAreaColors provides hoverAreaColors,
43 | LocalPressedAreaColors provides pressedAreaColors,
44 | content = content
45 | )
46 | }
47 | }
48 |
49 | val LocalActionButtonColors = compositionLocalOf {
50 | LightTheme.ActionButtonColors
51 | }
52 |
53 | @Composable
54 | fun ActionButton(
55 | onClick: () -> Unit,
56 | modifier: Modifier = Modifier,
57 | enabled: Boolean = true,
58 | shape: Shape = RoundedCornerShape(6.dp),
59 | indication: Indication? = HoverOrPressedIndication(shape),
60 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
61 | colors: ActionButtonColors = LocalActionButtonColors.current,
62 | content: @Composable BoxScope.() -> Unit,
63 | ) {
64 | colors.provideArea(enabled) {
65 | Box(
66 | modifier.areaBackground(shape = shape).clickable(
67 | interactionSource = interactionSource,
68 | indication = indication,
69 | enabled = enabled,
70 | onClick = onClick,
71 | role = Role.Button
72 | ),
73 | propagateMinConstraints = true
74 | ) {
75 | content()
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ContextCompositionLocals.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 |
5 | val LocalContentActivated = compositionLocalOf { true }
6 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Icon.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.geometry.Size
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.ColorFilter
11 | import androidx.compose.ui.graphics.ImageBitmap
12 | import androidx.compose.ui.graphics.isSpecified
13 | import androidx.compose.ui.graphics.painter.BitmapPainter
14 | import androidx.compose.ui.graphics.painter.Painter
15 | import androidx.compose.ui.graphics.toolingGraphicsLayer
16 | import androidx.compose.ui.graphics.vector.ImageVector
17 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
18 | import androidx.compose.ui.layout.ContentScale
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.semantics.Role
21 | import androidx.compose.ui.semantics.contentDescription
22 | import androidx.compose.ui.semantics.role
23 | import androidx.compose.ui.semantics.semantics
24 | import androidx.compose.ui.unit.dp
25 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
26 | import io.kanro.compose.jetbrains.expui.theme.LocalIsDarkTheme
27 |
28 | @Composable
29 | fun themedSvgResource(resource: String, isDark: Boolean = LocalIsDarkTheme.current): Painter {
30 | var realResource = resource
31 | if (isDark) {
32 | if (!realResource.endsWith("_dark.svg")) {
33 | val dark = realResource.replace(".svg", "_dark.svg")
34 | if (Thread.currentThread().contextClassLoader.getResource(dark) != null) {
35 | realResource = dark
36 | }
37 | }
38 | } else {
39 | if (realResource.endsWith("_dark.svg")) {
40 | val light = realResource.replace("_dark.svg", ".svg")
41 | if (Thread.currentThread().contextClassLoader.getResource(light) != null) {
42 | realResource = light
43 | }
44 | }
45 | }
46 | return painterResource(realResource)
47 | }
48 |
49 | private fun Modifier.defaultSizeFor(painter: Painter) = this.then(
50 | if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
51 | DefaultIconSizeModifier
52 | } else {
53 | Modifier
54 | }
55 | )
56 |
57 | private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
58 |
59 | private val DefaultIconSizeModifier = Modifier.size(20.dp)
60 |
61 | @Composable
62 | fun Icon(
63 | resource: String,
64 | contentDescription: String? = null,
65 | modifier: Modifier = Modifier,
66 | colorFilter: ColorFilter? = null,
67 | markerColor: Color = Color.Unspecified,
68 | ) {
69 | Icon(
70 | themedSvgResource(resource), contentDescription, modifier,
71 | colorFilter = colorFilter,
72 | markerColor = markerColor,
73 | )
74 | }
75 |
76 | @Composable
77 | fun Icon(
78 | bitmap: ImageBitmap,
79 | contentDescription: String? = null,
80 | modifier: Modifier = Modifier,
81 | colorFilter: ColorFilter? = null,
82 | markerColor: Color = Color.Unspecified,
83 | ) {
84 | val painter = remember(bitmap) { BitmapPainter(bitmap) }
85 | Icon(
86 | painter = painter,
87 | contentDescription = contentDescription,
88 | modifier = modifier,
89 | colorFilter = colorFilter,
90 | markerColor = markerColor,
91 | )
92 | }
93 |
94 | @Composable
95 | fun Icon(
96 | imageVector: ImageVector,
97 | contentDescription: String? = null,
98 | modifier: Modifier = Modifier,
99 | colorFilter: ColorFilter? = null,
100 | markerColor: Color = Color.Unspecified,
101 | ) {
102 | Icon(
103 | painter = rememberVectorPainter(imageVector),
104 | contentDescription = contentDescription,
105 | modifier = modifier,
106 | colorFilter = colorFilter,
107 | markerColor = markerColor,
108 | )
109 | }
110 |
111 | @Composable
112 | fun Icon(
113 | painter: Painter,
114 | contentDescription: String? = null,
115 | modifier: Modifier = Modifier,
116 | colorFilter: ColorFilter? = null,
117 | markerColor: Color = Color.Unspecified,
118 | ) {
119 | val semantics = if (contentDescription != null) {
120 | Modifier.semantics {
121 | this.contentDescription = contentDescription
122 | this.role = Role.Image
123 | }
124 | } else {
125 | Modifier
126 | }
127 | val filter = colorFilter ?: run {
128 | val foreground = LocalAreaColors.current.foreground
129 | if (foreground.isSpecified) {
130 | ColorFilter.tint(foreground)
131 | } else {
132 | null
133 | }
134 | }
135 | Box(
136 | modifier.toolingGraphicsLayer()
137 | .defaultSizeFor(painter)
138 | .paintWithMarker(painter, contentScale = ContentScale.None, colorFilter = filter, markerColor = markerColor)
139 | .then(semantics)
140 | )
141 | }
142 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Indication.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.interaction.InteractionSource
5 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
6 | import androidx.compose.foundation.interaction.collectIsPressedAsState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.State
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.Shape
12 | import androidx.compose.ui.graphics.drawOutline
13 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
14 | import androidx.compose.ui.graphics.isSpecified
15 | import io.kanro.compose.jetbrains.expui.style.LocalHoverAreaColors
16 | import io.kanro.compose.jetbrains.expui.style.LocalPressedAreaColors
17 |
18 | class HoverOrPressedIndication(private val shape: Shape) : Indication {
19 | private class IndicationInstance(
20 | private val shape: Shape,
21 | private val isHover: State,
22 | private val isPressed: State,
23 | private val hoverColor: Color,
24 | private val pressedColor: Color,
25 | ) : androidx.compose.foundation.IndicationInstance {
26 | override fun ContentDrawScope.drawIndication() {
27 | when {
28 | isPressed.value -> {
29 | if (pressedColor.isSpecified) {
30 | val outline = shape.createOutline(size, layoutDirection, this)
31 | drawOutline(outline, pressedColor)
32 | }
33 | }
34 |
35 | isHover.value -> {
36 | if (hoverColor.isSpecified) {
37 | val outline = shape.createOutline(size, layoutDirection, this)
38 | drawOutline(outline, hoverColor)
39 | }
40 | }
41 | }
42 | drawContent()
43 | }
44 | }
45 |
46 | @Composable
47 | override fun rememberUpdatedInstance(interactionSource: InteractionSource): androidx.compose.foundation.IndicationInstance {
48 | val hoverColors = LocalHoverAreaColors.current
49 | val pressedColors = LocalPressedAreaColors.current
50 | val isPressed = interactionSource.collectIsPressedAsState()
51 | val isHover = interactionSource.collectIsHoveredAsState()
52 |
53 | return remember(hoverColors, pressedColors, interactionSource) {
54 | IndicationInstance(
55 | shape, isHover, isPressed, hoverColors.startBackground, pressedColors.startBackground
56 | )
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Label.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.text.BasicText
4 | import androidx.compose.foundation.text.InlineTextContent
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.graphics.takeOrElse
9 | import androidx.compose.ui.text.AnnotatedString
10 | import androidx.compose.ui.text.TextLayoutResult
11 | import androidx.compose.ui.text.TextStyle
12 | import androidx.compose.ui.text.font.FontFamily
13 | import androidx.compose.ui.text.font.FontStyle
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.text.style.TextAlign
16 | import androidx.compose.ui.text.style.TextDecoration
17 | import androidx.compose.ui.text.style.TextOverflow
18 | import androidx.compose.ui.unit.TextUnit
19 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
20 | import io.kanro.compose.jetbrains.expui.style.LocalDefaultTextStyle
21 |
22 | @Composable
23 | fun Label(
24 | text: String,
25 | modifier: Modifier = Modifier,
26 | color: Color = Color.Unspecified,
27 | fontSize: TextUnit = TextUnit.Unspecified,
28 | fontStyle: FontStyle? = null,
29 | fontWeight: FontWeight? = null,
30 | fontFamily: FontFamily? = null,
31 | letterSpacing: TextUnit = TextUnit.Unspecified,
32 | textDecoration: TextDecoration? = null,
33 | textAlign: TextAlign? = null,
34 | lineHeight: TextUnit = TextUnit.Unspecified,
35 | overflow: TextOverflow = TextOverflow.Clip,
36 | softWrap: Boolean = true,
37 | maxLines: Int = Int.MAX_VALUE,
38 | onTextLayout: (TextLayoutResult) -> Unit = {},
39 | style: TextStyle = LocalDefaultTextStyle.current,
40 | ) {
41 | val textColor = color.takeOrElse {
42 | style.color
43 | }.takeOrElse {
44 | LocalAreaColors.current.text
45 | }
46 | val mergedStyle = style.merge(
47 | TextStyle(
48 | color = textColor,
49 | fontSize = fontSize,
50 | fontWeight = fontWeight,
51 | textAlign = textAlign,
52 | lineHeight = lineHeight,
53 | fontFamily = fontFamily,
54 | textDecoration = textDecoration,
55 | fontStyle = fontStyle,
56 | letterSpacing = letterSpacing
57 | )
58 | )
59 |
60 | BasicText(
61 | text = text,
62 | modifier = modifier,
63 | style = mergedStyle,
64 | onTextLayout = onTextLayout,
65 | overflow = overflow,
66 | softWrap = softWrap,
67 | maxLines = maxLines,
68 | )
69 | }
70 |
71 | @Composable
72 | fun Label(
73 | text: AnnotatedString,
74 | modifier: Modifier = Modifier,
75 | color: Color = Color.Unspecified,
76 | fontSize: TextUnit = TextUnit.Unspecified,
77 | fontStyle: FontStyle? = null,
78 | fontWeight: FontWeight? = null,
79 | fontFamily: FontFamily? = null,
80 | letterSpacing: TextUnit = TextUnit.Unspecified,
81 | textDecoration: TextDecoration? = null,
82 | textAlign: TextAlign? = null,
83 | lineHeight: TextUnit = TextUnit.Unspecified,
84 | overflow: TextOverflow = TextOverflow.Clip,
85 | softWrap: Boolean = true,
86 | maxLines: Int = Int.MAX_VALUE,
87 | inlineContent: Map = mapOf(),
88 | onTextLayout: (TextLayoutResult) -> Unit = {},
89 | style: TextStyle = LocalDefaultTextStyle.current,
90 | ) {
91 | val textColor = color.takeOrElse {
92 | style.color
93 | }.takeOrElse {
94 | LocalAreaColors.current.text
95 | }
96 | val mergedStyle = style.merge(
97 | TextStyle(
98 | color = textColor,
99 | fontSize = fontSize,
100 | fontWeight = fontWeight,
101 | textAlign = textAlign,
102 | lineHeight = lineHeight,
103 | fontFamily = fontFamily,
104 | textDecoration = textDecoration,
105 | fontStyle = fontStyle,
106 | letterSpacing = letterSpacing
107 | )
108 | )
109 |
110 | BasicText(
111 | text = text,
112 | modifier = modifier,
113 | style = mergedStyle,
114 | onTextLayout = onTextLayout,
115 | overflow = overflow,
116 | softWrap = softWrap,
117 | maxLines = maxLines,
118 | inlineContent = inlineContent
119 | )
120 | }
121 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/PointerInput.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.input.pointer.PointerEventType
5 | import androidx.compose.ui.input.pointer.pointerInput
6 |
7 | fun Modifier.onHover(onHover: (Boolean) -> Unit): Modifier = this.pointerInput(Unit) {
8 | awaitPointerEventScope {
9 | while (true) {
10 | val event = awaitPointerEvent()
11 | when (event.type) {
12 | PointerEventType.Enter -> onHover(true)
13 | PointerEventType.Exit -> onHover(false)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ProgressBar.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.animation.core.animateFloat
4 | import androidx.compose.animation.core.infiniteRepeatable
5 | import androidx.compose.animation.core.keyframes
6 | import androidx.compose.animation.core.rememberInfiniteTransition
7 | import androidx.compose.foundation.Canvas
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.progressSemantics
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.compositionLocalOf
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.geometry.Offset
15 | import androidx.compose.ui.graphics.Brush
16 | import androidx.compose.ui.graphics.StrokeCap
17 | import androidx.compose.ui.graphics.TileMode
18 | import androidx.compose.ui.unit.dp
19 | import io.kanro.compose.jetbrains.expui.style.AreaColors
20 | import io.kanro.compose.jetbrains.expui.style.AreaProvider
21 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
22 |
23 | class ProgressBarColors(
24 | override val normalAreaColors: AreaColors,
25 | val indeterminateAreaColors: AreaColors,
26 | ) : AreaProvider
27 |
28 | val LocalProgressBarColors = compositionLocalOf {
29 | LightTheme.ProgressBarColors
30 | }
31 |
32 | @Composable
33 | fun ProgressBar(
34 | progress: Float,
35 | modifier: Modifier = Modifier,
36 | colors: ProgressBarColors = LocalProgressBarColors.current,
37 | ) {
38 | val currentColors = colors.normalAreaColors
39 | Canvas(
40 | modifier.progressSemantics(progress).size(200.dp, 4.dp)
41 | ) {
42 | val strokeWidth = size.height
43 | val length = size.width
44 |
45 | drawLine(
46 | currentColors.startBackground,
47 | Offset(0f, strokeWidth / 2f),
48 | Offset(length, strokeWidth / 2f),
49 | strokeWidth,
50 | cap = StrokeCap.Round
51 | )
52 | drawLine(
53 | currentColors.foreground,
54 | Offset(0f, strokeWidth / 2f),
55 | Offset(length * progress, strokeWidth / 2f),
56 | strokeWidth,
57 | cap = StrokeCap.Round
58 | )
59 | }
60 | }
61 |
62 | @Composable
63 | fun ProgressBar(
64 | modifier: Modifier = Modifier,
65 | colors: ProgressBarColors = LocalProgressBarColors.current,
66 | ) {
67 | val transition = rememberInfiniteTransition()
68 | val currentOffset by transition.animateFloat(
69 | 0f, 1f,
70 | infiniteRepeatable(
71 | animation = keyframes {
72 | durationMillis = 1000
73 | }
74 | )
75 | )
76 | val currentColors = colors.indeterminateAreaColors
77 | Canvas(
78 | modifier.progressSemantics().size(200.dp, 4.dp)
79 | ) {
80 | val strokeWidth = size.height
81 | val length = size.width
82 | val offset = currentOffset * length
83 | val brush = Brush.linearGradient(
84 | listOf(currentColors.startBackground, currentColors.endBackground, currentColors.startBackground),
85 | start = Offset(offset, 0f),
86 | end = Offset(offset + length, 0f),
87 | tileMode = TileMode.Repeated
88 | )
89 | drawLine(
90 | brush, Offset(0f, strokeWidth / 2f), Offset(length, strokeWidth / 2f), strokeWidth, cap = StrokeCap.Round
91 | )
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/ToolBar.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.interaction.MutableInteractionSource
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.BoxScope
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.compositionLocalOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Shape
15 | import androidx.compose.ui.semantics.Role
16 | import androidx.compose.ui.unit.dp
17 | import io.kanro.compose.jetbrains.expui.style.AreaColors
18 | import io.kanro.compose.jetbrains.expui.style.AreaProvider
19 | import io.kanro.compose.jetbrains.expui.style.DisabledAreaProvider
20 | import io.kanro.compose.jetbrains.expui.style.HoverAreaProvider
21 | import io.kanro.compose.jetbrains.expui.style.InactiveSelectionAreaProvider
22 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
23 | import io.kanro.compose.jetbrains.expui.style.LocalDisabledAreaColors
24 | import io.kanro.compose.jetbrains.expui.style.LocalHoverAreaColors
25 | import io.kanro.compose.jetbrains.expui.style.LocalInactiveAreaColors
26 | import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
27 | import io.kanro.compose.jetbrains.expui.style.LocalPressedAreaColors
28 | import io.kanro.compose.jetbrains.expui.style.LocalSelectionAreaColors
29 | import io.kanro.compose.jetbrains.expui.style.LocalSelectionInactiveAreaColors
30 | import io.kanro.compose.jetbrains.expui.style.PressedAreaProvider
31 | import io.kanro.compose.jetbrains.expui.style.areaBackground
32 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
33 |
34 | class ToolBarActionButtonColors(
35 | override val normalAreaColors: AreaColors,
36 | override val hoverAreaColors: AreaColors,
37 | override val pressedAreaColors: AreaColors,
38 | override val disabledAreaColors: AreaColors,
39 | override val selectionAreaColors: AreaColors,
40 | override val inactiveAreaColors: AreaColors,
41 | override val inactiveSelectionAreaColors: AreaColors,
42 | ) : AreaProvider, HoverAreaProvider, PressedAreaProvider, DisabledAreaProvider, InactiveSelectionAreaProvider {
43 | @Composable
44 | fun provideArea(enabled: Boolean, selected: Boolean, content: @Composable () -> Unit) {
45 | val activated = LocalContentActivated.current
46 | val currentColors = when {
47 | !enabled -> disabledAreaColors
48 | selected -> if (activated) selectionAreaColors else inactiveSelectionAreaColors
49 | !activated -> inactiveAreaColors
50 | else -> normalAreaColors
51 | }
52 | CompositionLocalProvider(
53 | LocalAreaColors provides currentColors,
54 | LocalNormalAreaColors provides normalAreaColors,
55 | LocalDisabledAreaColors provides disabledAreaColors,
56 | LocalHoverAreaColors provides hoverAreaColors,
57 | LocalPressedAreaColors provides pressedAreaColors,
58 | LocalSelectionInactiveAreaColors provides inactiveSelectionAreaColors,
59 | LocalInactiveAreaColors provides inactiveAreaColors,
60 | LocalSelectionAreaColors provides selectionAreaColors,
61 | content = content
62 | )
63 | }
64 | }
65 |
66 | val LocalToolBarActionButtonColors = compositionLocalOf {
67 | LightTheme.ToolBarActionButtonColors
68 | }
69 |
70 | @Composable
71 | fun ToolBarActionButton(
72 | selected: Boolean = false,
73 | onClick: () -> Unit,
74 | modifier: Modifier = Modifier,
75 | enabled: Boolean = true,
76 | shape: Shape = RoundedCornerShape(6.dp),
77 | indication: Indication? = HoverOrPressedIndication(shape),
78 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
79 | colors: ToolBarActionButtonColors = LocalToolBarActionButtonColors.current,
80 | content: @Composable BoxScope.() -> Unit,
81 | ) {
82 | colors.provideArea(enabled, selected) {
83 | Box(
84 | modifier.clickable(
85 | interactionSource = interactionSource,
86 | indication = indication,
87 | enabled = enabled,
88 | onClick = onClick,
89 | role = Role.Button
90 | ).areaBackground(shape = shape),
91 | propagateMinConstraints = true
92 | ) {
93 | content()
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/control/Tooltip.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.control
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.TooltipArea
5 | import androidx.compose.foundation.TooltipPlacement
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.runtime.compositionLocalOf
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.shadow
15 | import androidx.compose.ui.unit.DpOffset
16 | import androidx.compose.ui.unit.dp
17 | import io.kanro.compose.jetbrains.expui.style.AreaColors
18 | import io.kanro.compose.jetbrains.expui.style.AreaProvider
19 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
20 | import io.kanro.compose.jetbrains.expui.style.LocalNormalAreaColors
21 | import io.kanro.compose.jetbrains.expui.style.areaBackground
22 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
23 | import io.kanro.compose.jetbrains.expui.theme.LocalIsDarkTheme
24 |
25 | class ToolTipColors(
26 | val isDark: Boolean,
27 | override val normalAreaColors: AreaColors,
28 | ) : AreaProvider {
29 | @Composable
30 | fun provideArea(content: @Composable () -> Unit) {
31 | CompositionLocalProvider(
32 | LocalAreaColors provides normalAreaColors,
33 | LocalNormalAreaColors provides normalAreaColors,
34 | LocalIsDarkTheme provides isDark,
35 | content = content
36 | )
37 | }
38 | }
39 |
40 | val LocalToolTipColors = compositionLocalOf {
41 | LightTheme.ToolTipColors
42 | }
43 |
44 | @Composable
45 | @OptIn(ExperimentalFoundationApi::class)
46 | fun Tooltip(
47 | tooltip: @Composable () -> Unit,
48 | modifier: Modifier = Modifier,
49 | delayMillis: Int = 500,
50 | tooltipPlacement: TooltipPlacement = TooltipPlacement.CursorPoint(
51 | offset = DpOffset(0.dp, 32.dp)
52 | ),
53 | colors: ToolTipColors = LocalToolTipColors.current,
54 | content: @Composable () -> Unit,
55 | ) {
56 | TooltipArea(
57 | {
58 | colors.provideArea {
59 | Box(
60 | modifier = Modifier.shadow(8.dp).areaBackground()
61 | .border(1.dp, LocalAreaColors.current.startBorderColor),
62 | contentAlignment = Alignment.Center
63 | ) {
64 | Box(
65 | modifier = Modifier.padding(16.dp),
66 | ) {
67 | tooltip()
68 | }
69 | }
70 | }
71 | }, modifier, delayMillis, tooltipPlacement, content
72 | )
73 | }
74 |
75 | @Composable
76 | @OptIn(ExperimentalFoundationApi::class)
77 | fun Tooltip(
78 | tooltip: String,
79 | modifier: Modifier = Modifier,
80 | delayMillis: Int = 500,
81 | tooltipPlacement: TooltipPlacement = TooltipPlacement.CursorPoint(
82 | offset = DpOffset(0.dp, 32.dp)
83 | ),
84 | colors: ToolTipColors = LocalToolTipColors.current,
85 | content: @Composable () -> Unit,
86 | ) {
87 | Tooltip({
88 | Label(tooltip)
89 | }, modifier, delayMillis, tooltipPlacement, colors, content)
90 | }
91 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/AreaColors.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.style
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.compositionLocalOf
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Brush
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.RectangleShape
11 | import androidx.compose.ui.graphics.Shape
12 | import androidx.compose.ui.graphics.isUnspecified
13 | import androidx.compose.ui.unit.Dp
14 | import androidx.compose.ui.unit.dp
15 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
16 |
17 | /**
18 | * Color definition for an area which has background and foreground.
19 | */
20 | data class AreaColors(
21 | /**
22 | * Text foreground color.
23 | */
24 | val text: Color,
25 | /**
26 | * Overriding the foreground colour for some components that have their own colour like [Icon][io.kanro.compose.jetbrains.expui.control.Icon].
27 | */
28 | val foreground: Color,
29 | val startBackground: Color,
30 | val endBackground: Color,
31 | val startBorderColor: Color,
32 | val endBorderColor: Color,
33 | val focusColor: Color,
34 | )
35 |
36 | @Composable
37 | fun Modifier.areaBackground(areaColors: AreaColors = LocalAreaColors.current, shape: Shape = RectangleShape): Modifier {
38 | return background(areaColors, shape)
39 | }
40 |
41 | fun Modifier.background(areaColors: AreaColors, shape: Shape = RectangleShape): Modifier {
42 | if (areaColors.startBackground.isUnspecified) {
43 | return this
44 | }
45 | if (areaColors.endBackground.isUnspecified || areaColors.startBackground == areaColors.endBackground) {
46 | return this.background(areaColors.startBackground, shape)
47 | }
48 | return this.background(Brush.linearGradient(listOf(areaColors.startBackground, areaColors.endBackground)), shape)
49 | }
50 |
51 | @Composable
52 | fun Modifier.areaBorder(
53 | areaColors: AreaColors = LocalAreaColors.current,
54 | width: Dp = 1.dp,
55 | shape: Shape = RectangleShape,
56 | ): Modifier {
57 | return border(areaColors, width, shape)
58 | }
59 |
60 | fun Modifier.border(areaColors: AreaColors, width: Dp = 1.dp, shape: Shape = RectangleShape): Modifier {
61 | if (areaColors.startBorderColor.isUnspecified) {
62 | return this
63 | }
64 | if (areaColors.endBorderColor.isUnspecified || areaColors.startBorderColor == areaColors.endBorderColor) {
65 | return this.border(width, areaColors.startBorderColor, shape)
66 | }
67 | return this.border(width, Brush.linearGradient(listOf(areaColors.startBackground, areaColors.endBackground)), shape)
68 | }
69 |
70 | @Composable
71 | fun Modifier.areaFocusBorder(
72 | focused: Boolean,
73 | areaColors: AreaColors = LocalAreaColors.current,
74 | width: Dp = 2.dp,
75 | shape: Shape = RectangleShape,
76 | ): Modifier {
77 | return focusBorder(focused, areaColors, width, shape)
78 | }
79 |
80 | fun Modifier.focusBorder(
81 | focused: Boolean,
82 | areaColors: AreaColors,
83 | width: Dp = 2.dp,
84 | shape: Shape = RectangleShape,
85 | ): Modifier {
86 | if (!focused) return this
87 | if (areaColors.focusColor.isUnspecified) {
88 | return this
89 | }
90 | return this.outerBorder(width, areaColors.focusColor, shape)
91 | }
92 |
93 | val LocalAreaColors = compositionLocalOf {
94 | LightTheme.NormalAreaColors
95 | }
96 |
97 | val LocalNormalAreaColors = compositionLocalOf {
98 | LightTheme.NormalAreaColors
99 | }
100 |
101 | val LocalInactiveAreaColors = compositionLocalOf {
102 | LightTheme.InactiveAreaColors
103 | }
104 |
105 | val LocalErrorAreaColors = compositionLocalOf {
106 | LightTheme.ErrorAreaColors
107 | }
108 |
109 | val LocalErrorInactiveAreaColors = compositionLocalOf {
110 | LightTheme.ErrorInactiveAreaColors
111 | }
112 |
113 | val LocalDisabledAreaColors = compositionLocalOf {
114 | LightTheme.DisabledAreaColors
115 | }
116 |
117 | val LocalHoverAreaColors = compositionLocalOf {
118 | LightTheme.HoverAreaColors
119 | }
120 |
121 | val LocalPressedAreaColors = compositionLocalOf {
122 | LightTheme.PressedAreaColors
123 | }
124 |
125 | val LocalFocusAreaColors = compositionLocalOf {
126 | LightTheme.FocusAreaColors
127 | }
128 |
129 | val LocalSelectionAreaColors = compositionLocalOf {
130 | LightTheme.SelectionAreaColors
131 | }
132 |
133 | val LocalSelectionInactiveAreaColors = compositionLocalOf {
134 | LightTheme.SelectionInactiveAreaColors
135 | }
136 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/AreaProvider.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.style
2 |
3 | interface AreaProvider {
4 | val normalAreaColors: AreaColors
5 | }
6 |
7 | interface ErrorAreProvider : AreaProvider {
8 | val errorAreaColors: AreaColors
9 | }
10 |
11 | interface ErrorFocusAreaProvider : ErrorAreProvider, FocusAreaProvider {
12 | val errorFocusAreaColors: AreaColors
13 | }
14 |
15 | interface SelectionAreaProvider : AreaProvider {
16 | val selectionAreaColors: AreaColors
17 | }
18 |
19 | interface FocusAreaProvider : AreaProvider {
20 | val focusAreaColors: AreaColors
21 | }
22 |
23 | interface DisabledAreaProvider : AreaProvider {
24 | val disabledAreaColors: AreaColors
25 | }
26 |
27 | interface HoverAreaProvider : AreaProvider {
28 | val hoverAreaColors: AreaColors
29 | }
30 |
31 | interface PressedAreaProvider : AreaProvider {
32 | val pressedAreaColors: AreaColors
33 | }
34 |
35 | interface InactiveAreaProvider : AreaProvider {
36 | val inactiveAreaColors: AreaColors
37 | }
38 |
39 | interface InactiveErrorAreaProvider : ErrorAreProvider, InactiveAreaProvider {
40 | val inactiveErrorAreaColors: AreaColors
41 | }
42 |
43 | interface InactiveSelectionAreaProvider : SelectionAreaProvider, InactiveAreaProvider {
44 | val inactiveSelectionAreaColors: AreaColors
45 | }
46 |
47 | interface InactiveFocusAreaProvider : FocusAreaProvider, InactiveAreaProvider {
48 | val inactiveFocusAreaColors: AreaColors
49 | }
50 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/style/TextStyle.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.style
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
5 |
6 | val LocalDefaultTextStyle = compositionLocalOf {
7 | LightTheme.DefaultTextStyle
8 | }
9 |
10 | val LocalDefaultBoldTextStyle = compositionLocalOf {
11 | LightTheme.DefaultBoldTextStyle
12 | }
13 |
14 | val LocalParagraphTextStyle = compositionLocalOf {
15 | LightTheme.ParagraphTextStyle
16 | }
17 |
18 | val LocalMediumTextStyle = compositionLocalOf {
19 | LightTheme.MediumTextStyle
20 | }
21 |
22 | val LocalMediumBoldTextStyle = compositionLocalOf {
23 | LightTheme.MediumBoldTextStyle
24 | }
25 |
26 | val LocalSmallTextStyle = compositionLocalOf {
27 | LightTheme.SmallTextStyle
28 | }
29 |
30 | val LocalH0TextStyle = compositionLocalOf {
31 | LightTheme.H0TextStyle
32 | }
33 |
34 | val LocalH1TextStyle = compositionLocalOf {
35 | LightTheme.H1TextStyle
36 | }
37 |
38 | val LocalH2TextStyle = compositionLocalOf {
39 | LightTheme.H2TextStyle
40 | }
41 |
42 | val LocalH2BoldTextStyle = compositionLocalOf {
43 | LightTheme.H2BoldTextStyle
44 | }
45 |
46 | val LocalH3TextStyle = compositionLocalOf {
47 | LightTheme.H3TextStyle
48 | }
49 |
50 | val LocalH3BoldTextStyle = compositionLocalOf {
51 | LightTheme.H3BoldTextStyle
52 | }
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/Fonts.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.theme
2 |
3 | import androidx.compose.ui.text.font.FontFamily
4 | import androidx.compose.ui.text.font.FontWeight
5 | import androidx.compose.ui.text.platform.Font
6 |
7 | object Fonts {
8 | val InterRegular = Font(resource = "/fonts/Inter-Regular.ttf", weight = FontWeight.Normal)
9 |
10 | val InterBold = Font(resource = "/fonts/Inter-Bold.ttf", weight = FontWeight.Bold)
11 |
12 | val InterMedium = Font(resource = "/fonts/Inter-Medium.ttf", weight = FontWeight.Medium)
13 |
14 | val InterSemiBold = Font(resource = "/fonts/Inter-SemiBold.ttf", weight = FontWeight.SemiBold)
15 |
16 | val InterLight = Font(resource = "/fonts/Inter-Light.ttf", weight = FontWeight.Light)
17 |
18 | val InterThin = Font(resource = "/fonts/Inter-Thin.ttf", weight = FontWeight.Thin)
19 |
20 | val InterExtraLight = Font(resource = "/fonts/Inter-ExtraLight.ttf", weight = FontWeight.ExtraLight)
21 |
22 | val InterExtraBold = Font(resource = "/fonts/Inter-ExtraBold.ttf", weight = FontWeight.ExtraBold)
23 |
24 | val InterBlack = Font(resource = "/fonts/Inter-Black.ttf", weight = FontWeight.Black)
25 |
26 | val Inter = FontFamily(
27 | InterRegular,
28 | InterBold,
29 | InterMedium,
30 | InterSemiBold,
31 | InterLight,
32 | InterThin,
33 | InterExtraLight,
34 | InterExtraBold
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.theme
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.runtime.ProvidedValue
6 | import androidx.compose.runtime.compositionLocalOf
7 |
8 | val LocalIsDarkTheme = compositionLocalOf { false }
9 |
10 | interface Theme {
11 | val isDark: Boolean
12 |
13 | @Composable
14 | fun provide(content: @Composable () -> Unit) {
15 | CompositionLocalProvider(
16 | *provideValues(),
17 | content = content,
18 | )
19 | }
20 |
21 | fun provideValues(): Array>
22 | }
23 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/CustomWindowDecorationAccessing.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.util
2 |
3 | import java.awt.Shape
4 | import java.awt.Window
5 | import java.lang.reflect.Method
6 |
7 | internal object CustomWindowDecorationAccessing {
8 | init {
9 | UnsafeAccessing.assignAccessibility(
10 | UnsafeAccessing.desktopModule,
11 | listOf(
12 | "java.awt"
13 | )
14 | )
15 | }
16 |
17 | private val customWindowDecorationInstance: Any? = try {
18 | val customWindowDecoration = Class.forName("java.awt.Window\$CustomWindowDecoration")
19 | val constructor = customWindowDecoration.declaredConstructors.first()
20 | constructor.isAccessible = true
21 | constructor.newInstance()
22 | } catch (e: Exception) {
23 | null
24 | }
25 |
26 | private val setCustomDecorationEnabledMethod: Method? =
27 | getMethod("setCustomDecorationEnabled", Window::class.java, Boolean::class.java)
28 |
29 | private val setCustomDecorationTitleBarHeightMethod: Method? =
30 | getMethod("setCustomDecorationTitleBarHeight", Window::class.java, Int::class.java)
31 |
32 | private val setCustomDecorationHitTestSpotsMethod: Method? =
33 | getMethod("setCustomDecorationHitTestSpots", Window::class.java, MutableList::class.java)
34 |
35 | private fun getMethod(name: String, vararg params: Class<*>): Method? {
36 | return try {
37 | val clazz = Class.forName("java.awt.Window\$CustomWindowDecoration")
38 | val method = clazz.getDeclaredMethod(
39 | name, *params
40 | )
41 | method.isAccessible = true
42 | method
43 | } catch (e: Exception) {
44 | null
45 | }
46 | }
47 |
48 | fun setCustomDecorationEnabled(window: Window, enabled: Boolean) {
49 | val instance = customWindowDecorationInstance ?: return
50 | val method = setCustomDecorationEnabledMethod ?: return
51 | method.invoke(instance, window, enabled)
52 | }
53 |
54 | fun setCustomDecorationTitleBarHeight(window: Window, height: Int) {
55 | val instance = customWindowDecorationInstance ?: return
56 | val method = setCustomDecorationTitleBarHeightMethod ?: return
57 | method.invoke(instance, window, height)
58 | }
59 |
60 | fun setCustomDecorationHitTestSpotsMethod(window: Window, spots: Map) {
61 | val instance = customWindowDecorationInstance ?: return
62 | val method = setCustomDecorationHitTestSpotsMethod ?: return
63 | method.invoke(instance, window, spots.entries.toMutableList())
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/UnsafeAccessing.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.util
2 |
3 | import sun.misc.Unsafe
4 | import java.lang.reflect.AccessibleObject
5 |
6 | internal object UnsafeAccessing {
7 | private val unsafe: Any? by lazy {
8 | try {
9 | val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe")
10 | theUnsafe.isAccessible = true
11 | theUnsafe.get(null) as Unsafe
12 | } catch (e: Throwable) {
13 | null
14 | }
15 | }
16 |
17 | val desktopModule by lazy {
18 | ModuleLayer.boot().findModule("java.desktop").get()
19 | }
20 |
21 | val ownerModule by lazy {
22 | this.javaClass.module
23 | }
24 |
25 | private val isAccessibleFieldOffset: Long? by lazy {
26 | try {
27 | (unsafe as? Unsafe)?.objectFieldOffset(Parent::class.java.getDeclaredField("first"))
28 | } catch (e: Throwable) {
29 | null
30 | }
31 | }
32 |
33 | private val implAddOpens by lazy {
34 | try {
35 | Module::class.java.getDeclaredMethod(
36 | "implAddOpens", String::class.java, Module::class.java
37 | ).accessible()
38 | } catch (e: Throwable) {
39 | null
40 | }
41 | }
42 |
43 | fun assignAccessibility(obj: AccessibleObject) {
44 | try {
45 | val theUnsafe = unsafe as? Unsafe ?: return
46 | val offset = isAccessibleFieldOffset ?: return
47 | theUnsafe.putBooleanVolatile(obj, offset, true)
48 | } catch (e: Throwable) {
49 | // ignore
50 | }
51 | }
52 |
53 | fun assignAccessibility(module: Module, packages: List) {
54 | try {
55 | packages.forEach {
56 | implAddOpens?.invoke(module, it, ownerModule)
57 | }
58 | } catch (e: Throwable) {
59 | // ignore
60 | }
61 | }
62 |
63 | private class Parent {
64 | var first = false
65 |
66 | @Volatile
67 | var second: Any? = null
68 | }
69 | }
70 |
71 | internal fun T.accessible(): T {
72 | return apply {
73 | UnsafeAccessing.assignAccessibility(this)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.Linux.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.input.key.KeyEvent
14 | import androidx.compose.ui.platform.LocalWindowInfo
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.window.FrameWindowScope
17 | import androidx.compose.ui.window.Window
18 | import androidx.compose.ui.window.WindowState
19 | import androidx.compose.ui.window.rememberWindowState
20 | import io.kanro.compose.jetbrains.expui.control.LocalContentActivated
21 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
22 | import io.kanro.compose.jetbrains.expui.style.areaBackground
23 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
24 | import io.kanro.compose.jetbrains.expui.theme.Theme
25 |
26 | @Composable
27 | internal fun JBWindowOnLinux(
28 | onCloseRequest: () -> Unit,
29 | state: WindowState = rememberWindowState(),
30 | visible: Boolean = true,
31 | title: String = "",
32 | theme: Theme = LightTheme,
33 | resizable: Boolean = true,
34 | enabled: Boolean = true,
35 | focusable: Boolean = true,
36 | alwaysOnTop: Boolean = false,
37 | onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
38 | onKeyEvent: (KeyEvent) -> Boolean = { false },
39 | mainToolBar: (@Composable MainToolBarScope.() -> Unit)?,
40 | content: @Composable FrameWindowScope.() -> Unit,
41 | ) {
42 | Window(
43 | onCloseRequest,
44 | state,
45 | visible,
46 | title,
47 | null,
48 | false,
49 | false,
50 | resizable,
51 | enabled,
52 | focusable,
53 | alwaysOnTop,
54 | onPreviewKeyEvent,
55 | onKeyEvent
56 | ) {
57 | CompositionLocalProvider(
58 | LocalWindow provides window,
59 | LocalContentActivated provides LocalWindowInfo.current.isWindowFocused,
60 | *theme.provideValues()
61 | ) {
62 | Column(Modifier.fillMaxSize()) {
63 | MainToolBarOnLinux(content = mainToolBar)
64 | Spacer(Modifier.fillMaxWidth().height(1.dp).background(LocalAreaColors.current.startBorderColor))
65 | Box(Modifier.fillMaxSize().areaBackground()) {
66 | content()
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.MacOS.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.runtime.DisposableEffect
13 | import androidx.compose.runtime.LaunchedEffect
14 | import androidx.compose.runtime.State
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.mutableStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.input.key.KeyEvent
20 | import androidx.compose.ui.platform.LocalWindowInfo
21 | import androidx.compose.ui.unit.dp
22 | import androidx.compose.ui.window.FrameWindowScope
23 | import androidx.compose.ui.window.Window
24 | import androidx.compose.ui.window.WindowPlacement
25 | import androidx.compose.ui.window.WindowState
26 | import androidx.compose.ui.window.rememberWindowState
27 | import io.kanro.compose.jetbrains.expui.control.LocalContentActivated
28 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
29 | import io.kanro.compose.jetbrains.expui.style.areaBackground
30 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
31 | import io.kanro.compose.jetbrains.expui.theme.Theme
32 | import java.awt.event.ComponentEvent
33 | import java.awt.event.ComponentListener
34 |
35 | @Composable
36 | internal fun JBWindowOnMacOS(
37 | onCloseRequest: () -> Unit,
38 | state: WindowState = rememberWindowState(),
39 | visible: Boolean = true,
40 | title: String = "",
41 | showTitle: Boolean = true,
42 | theme: Theme = LightTheme,
43 | resizable: Boolean = true,
44 | enabled: Boolean = true,
45 | focusable: Boolean = true,
46 | alwaysOnTop: Boolean = false,
47 | onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
48 | onKeyEvent: (KeyEvent) -> Boolean = { false },
49 | mainToolBar: (@Composable MainToolBarScope.() -> Unit)?,
50 | content: @Composable FrameWindowScope.() -> Unit,
51 | ) {
52 | Window(
53 | onCloseRequest,
54 | state,
55 | visible,
56 | title,
57 | null,
58 | false,
59 | false,
60 | resizable,
61 | enabled,
62 | focusable,
63 | alwaysOnTop,
64 | onPreviewKeyEvent,
65 | onKeyEvent
66 | ) {
67 | LaunchedEffect(Unit, theme) {
68 | val rootPane = window.rootPane
69 | rootPane.putClientProperty(
70 | "apple.awt.windowAppearance",
71 | if (theme.isDark) "NSAppearanceNameVibrantDark" else "NSAppearanceNameVibrantLight"
72 | )
73 | }
74 | CompositionLocalProvider(
75 | LocalWindow provides window,
76 | LocalContentActivated provides LocalWindowInfo.current.isWindowFocused,
77 | *theme.provideValues()
78 | ) {
79 | Column(Modifier.fillMaxSize()) {
80 | val isFullscreen by rememberWindowIsFullscreen()
81 | MainToolBarOnMacOS(title, showTitle, isFullscreen, content = mainToolBar)
82 | Spacer(Modifier.fillMaxWidth().height(1.dp).background(LocalAreaColors.current.startBorderColor))
83 | Box(Modifier.fillMaxSize().areaBackground()) {
84 | content()
85 | }
86 | }
87 | }
88 | }
89 | }
90 |
91 | @Composable
92 | fun FrameWindowScope.rememberWindowIsFullscreen(): State {
93 | val isFullscreen = remember {
94 | mutableStateOf(window.placement == WindowPlacement.Fullscreen)
95 | }
96 | DisposableEffect(window) {
97 | val listener = object : ComponentListener {
98 | override fun componentResized(e: ComponentEvent?) {
99 | isFullscreen.value = window.placement == WindowPlacement.Fullscreen
100 | }
101 |
102 | override fun componentMoved(e: ComponentEvent?) {}
103 |
104 | override fun componentShown(e: ComponentEvent?) {}
105 |
106 | override fun componentHidden(e: ComponentEvent?) {}
107 | }
108 | window.addComponentListener(listener)
109 | onDispose {
110 | window.removeComponentListener(listener)
111 | }
112 | }
113 | return isFullscreen
114 | }
115 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.Windows.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.painter.Painter
14 | import androidx.compose.ui.input.key.KeyEvent
15 | import androidx.compose.ui.platform.LocalWindowInfo
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.window.FrameWindowScope
18 | import androidx.compose.ui.window.Window
19 | import androidx.compose.ui.window.WindowState
20 | import androidx.compose.ui.window.rememberWindowState
21 | import io.kanro.compose.jetbrains.expui.control.LocalContentActivated
22 | import io.kanro.compose.jetbrains.expui.style.LocalAreaColors
23 | import io.kanro.compose.jetbrains.expui.style.areaBackground
24 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
25 | import io.kanro.compose.jetbrains.expui.theme.Theme
26 |
27 | @Composable
28 | internal fun JBWindowOnWindows(
29 | onCloseRequest: () -> Unit,
30 | state: WindowState = rememberWindowState(),
31 | visible: Boolean = true,
32 | title: String = "",
33 | showTitle: Boolean = true,
34 | theme: Theme = LightTheme,
35 | icon: Painter? = null,
36 | resizable: Boolean = true,
37 | enabled: Boolean = true,
38 | focusable: Boolean = true,
39 | alwaysOnTop: Boolean = false,
40 | onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
41 | onKeyEvent: (KeyEvent) -> Boolean = { false },
42 | mainToolBar: (@Composable MainToolBarScope.() -> Unit)?,
43 | content: @Composable FrameWindowScope.() -> Unit,
44 | ) {
45 | Window(
46 | onCloseRequest,
47 | state,
48 | visible,
49 | title,
50 | icon,
51 | false,
52 | false,
53 | resizable,
54 | enabled,
55 | focusable,
56 | alwaysOnTop,
57 | onPreviewKeyEvent,
58 | onKeyEvent
59 | ) {
60 | CompositionLocalProvider(
61 | LocalWindow provides window,
62 | LocalContentActivated provides LocalWindowInfo.current.isWindowFocused,
63 | *theme.provideValues()
64 | ) {
65 | Column(Modifier.fillMaxSize()) {
66 | MainToolBarOnWindows(icon, state, onCloseRequest, title, showTitle, resizable, content = mainToolBar)
67 | Spacer(Modifier.fillMaxWidth().height(1.dp).background(LocalAreaColors.current.startBorderColor))
68 | Box(Modifier.fillMaxSize().areaBackground()) {
69 | content()
70 | }
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/JBWindow.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.compositionLocalOf
5 | import androidx.compose.ui.graphics.painter.Painter
6 | import androidx.compose.ui.input.key.KeyEvent
7 | import androidx.compose.ui.res.painterResource
8 | import androidx.compose.ui.window.FrameWindowScope
9 | import androidx.compose.ui.window.WindowState
10 | import androidx.compose.ui.window.rememberWindowState
11 | import io.kanro.compose.jetbrains.expui.DesktopPlatform
12 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
13 | import io.kanro.compose.jetbrains.expui.theme.Theme
14 | import javax.swing.JFrame
15 |
16 | val LocalWindow = compositionLocalOf {
17 | error("CompositionLocal LocalWindow not provided")
18 | }
19 |
20 | @Composable
21 | fun JBWindow(
22 | onCloseRequest: () -> Unit,
23 | state: WindowState = rememberWindowState(),
24 | visible: Boolean = true,
25 | title: String = "",
26 | showTitle: Boolean = true,
27 | theme: Theme = LightTheme,
28 | icon: Painter? = painterResource("icons/compose.svg"),
29 | resizable: Boolean = true,
30 | enabled: Boolean = true,
31 | focusable: Boolean = true,
32 | alwaysOnTop: Boolean = false,
33 | onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
34 | onKeyEvent: (KeyEvent) -> Boolean = { false },
35 | mainToolBar: (@Composable MainToolBarScope.() -> Unit)? = null,
36 | content: @Composable FrameWindowScope.() -> Unit,
37 | ) {
38 | when (DesktopPlatform.Current) {
39 | DesktopPlatform.Linux -> JBWindowOnLinux(
40 | onCloseRequest,
41 | state,
42 | visible,
43 | title,
44 | theme,
45 | resizable,
46 | enabled,
47 | focusable,
48 | alwaysOnTop,
49 | onPreviewKeyEvent,
50 | onKeyEvent,
51 | mainToolBar,
52 | content
53 | )
54 |
55 | DesktopPlatform.Windows -> JBWindowOnWindows(
56 | onCloseRequest,
57 | state,
58 | visible,
59 | title,
60 | showTitle,
61 | theme,
62 | icon,
63 | resizable,
64 | enabled,
65 | focusable,
66 | alwaysOnTop,
67 | onPreviewKeyEvent,
68 | onKeyEvent,
69 | mainToolBar,
70 | content
71 | )
72 |
73 | DesktopPlatform.MacOS -> JBWindowOnMacOS(
74 | onCloseRequest,
75 | state,
76 | visible,
77 | title,
78 | showTitle,
79 | theme,
80 | resizable,
81 | enabled,
82 | focusable,
83 | alwaysOnTop,
84 | onPreviewKeyEvent,
85 | onKeyEvent,
86 | mainToolBar,
87 | content
88 | )
89 |
90 | DesktopPlatform.Unknown -> TODO()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Basic.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.layout.Layout
8 | import androidx.compose.ui.unit.dp
9 | import androidx.compose.ui.window.FrameWindowScope
10 | import io.kanro.compose.jetbrains.expui.control.LocalContentActivated
11 | import io.kanro.compose.jetbrains.expui.style.areaBackground
12 |
13 | @Composable
14 | fun FrameWindowScope.BasicMainToolBar(
15 | colors: MainToolBarColors = LocalMainToolBarColors.current,
16 | content: (@Composable MainToolBarScope.() -> Unit)?,
17 | ) {
18 | colors.provideArea(LocalContentActivated.current) {
19 | Layout(
20 | content = {
21 | with(MainToolBarScopeInstance) {
22 | content?.invoke(this)
23 | }
24 | },
25 | modifier = Modifier.fillMaxWidth().height(40.dp).areaBackground(),
26 | measurePolicy = rememberMainToolBarMeasurePolicy(window)
27 | )
28 | }
29 | }
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Linux.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.window.FrameWindowScope
5 |
6 | @Composable
7 | internal fun FrameWindowScope.MainToolBarOnLinux(
8 | colors: MainToolBarColors = LocalMainToolBarColors.current,
9 | content: (@Composable MainToolBarScope.() -> Unit)?,
10 | ) {
11 | BasicMainToolBar(colors, content)
12 | }
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.MacOS.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.width
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 | import androidx.compose.ui.window.FrameWindowScope
10 |
11 | @Composable
12 | internal fun FrameWindowScope.MainToolBarOnMacOS(
13 | title: String,
14 | showTitle: Boolean,
15 | isFullScreen: Boolean,
16 | colors: MainToolBarColors = LocalMainToolBarColors.current,
17 | content: (@Composable MainToolBarScope.() -> Unit)?,
18 | ) {
19 | BasicMainToolBar(colors) {
20 | if (isFullScreen) {
21 | Spacer(Modifier.width(80.dp).mainToolBarItem(Alignment.Start, true))
22 | }
23 | if (showTitle) {
24 | MainToolBarTitle(title)
25 | }
26 | content?.invoke(this)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/window/MainToolBar.Windows.kt:
--------------------------------------------------------------------------------
1 | package io.kanro.compose.jetbrains.expui.window
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.CompositionLocalProvider
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.focus.focusProperties
11 | import androidx.compose.ui.graphics.RectangleShape
12 | import androidx.compose.ui.graphics.painter.Painter
13 | import androidx.compose.ui.unit.dp
14 | import androidx.compose.ui.window.FrameWindowScope
15 | import androidx.compose.ui.window.WindowPlacement
16 | import androidx.compose.ui.window.WindowState
17 | import io.kanro.compose.jetbrains.expui.control.ActionButton
18 | import io.kanro.compose.jetbrains.expui.control.Icon
19 | import io.kanro.compose.jetbrains.expui.control.LocalActionButtonColors
20 | import io.kanro.compose.jetbrains.expui.control.LocalContentActivated
21 | import io.kanro.compose.jetbrains.expui.theme.LightTheme
22 |
23 | @Composable
24 | internal fun FrameWindowScope.MainToolBarOnWindows(
25 | icon: Painter?,
26 | windowState: WindowState,
27 | onCloseRequest: () -> Unit,
28 | title: String,
29 | showTitle: Boolean,
30 | resizeable: Boolean,
31 | colors: MainToolBarColors = LocalMainToolBarColors.current,
32 | content: (@Composable MainToolBarScope.() -> Unit)?,
33 | ) {
34 | BasicMainToolBar(colors) {
35 | if (icon != null) {
36 | Box(
37 | modifier = Modifier.size(40.dp).mainToolBarItem(Alignment.Start, true),
38 | contentAlignment = Alignment.Center
39 | ) {
40 | Icon(icon)
41 | }
42 | }
43 | WindowsSystemButtons(windowState, resizeable, onCloseRequest)
44 | if (showTitle) {
45 | MainToolBarTitle(title)
46 | }
47 | content?.invoke(this)
48 | }
49 | }
50 |
51 | val LocalWindowsCloseWindowButtonColors = compositionLocalOf {
52 | LightTheme.WindowsCloseWindowButtonColors
53 | }
54 |
55 | @Composable
56 | private fun MainToolBarScope.WindowsSystemButtons(
57 | windowState: WindowState,
58 | resizeable: Boolean,
59 | onCloseRequest: () -> Unit,
60 | ) {
61 | val active = LocalContentActivated.current
62 | CompositionLocalProvider(
63 | LocalActionButtonColors provides LocalWindowsCloseWindowButtonColors.current
64 | ) {
65 | ActionButton(
66 | { onCloseRequest() },
67 | Modifier.focusProperties { canFocus = false }.size(40.dp).mainToolBarItem(Alignment.End),
68 | shape = RectangleShape
69 | ) {
70 | if (active) {
71 | Icon("icons/windows/closeActive.svg")
72 | } else {
73 | Icon("icons/windows/closeInactive.svg")
74 | }
75 | }
76 | }
77 | ActionButton(
78 | {
79 | windowState.placement = when (windowState.placement) {
80 | WindowPlacement.Floating -> WindowPlacement.Maximized
81 | WindowPlacement.Maximized -> WindowPlacement.Floating
82 | WindowPlacement.Fullscreen -> WindowPlacement.Fullscreen
83 | }
84 | },
85 | Modifier.focusProperties { canFocus = false }.size(40.dp).mainToolBarItem(Alignment.End),
86 | enabled = resizeable,
87 | shape = RectangleShape
88 | ) {
89 | if (windowState.placement == WindowPlacement.Floating) {
90 | if (active) {
91 | Icon("icons/windows/maximize.svg")
92 | } else {
93 | Icon("icons/windows/maximizeInactive.svg")
94 | }
95 | } else {
96 | if (active) {
97 | Icon("icons/windows/restore.svg")
98 | } else {
99 | Icon("icons/windows/restoreInactive.svg")
100 | }
101 | }
102 | }
103 | ActionButton(
104 | { windowState.isMinimized = true },
105 | Modifier.focusProperties { canFocus = false }.size(40.dp).mainToolBarItem(Alignment.End),
106 | shape = RectangleShape
107 | ) {
108 | if (active) {
109 | Icon("icons/windows/minimize.svg")
110 | } else {
111 | Icon("icons/windows/minimizeInactive.svg")
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Black.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Bold.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-ExtraBold.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-ExtraLight.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Light.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Medium.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Regular.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-SemiBold.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/fonts/Inter-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/expui/src/main/resources/fonts/Inter-Thin.ttf
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/buttonDropTriangle.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/buttonDropTriangle_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/closeSmall.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/closeSmallHovered.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/closeSmallHovered_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/closeSmall_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/external_link_arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/external_link_arrow_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/linkDropTriangle.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/linkDropTriangle_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/closeActive.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/closeInactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/collapse.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/maximize.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/maximizeInactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/minimizeInactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/restore.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/expui/src/main/resources/icons/windows/restoreInactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ButterCam/compose-jetbrains-theme/8f237fd0c144ee8cc425aff5430f48ae634e914e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenLocal()
4 | gradlePluginPortal()
5 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
6 | }
7 | }
8 |
9 | include("classic")
10 | include("expui")
11 | include("expui-gallery")
12 |
13 | project(":classic").name = "compose-jetbrains-theme"
14 | project(":expui").name = "compose-jetbrains-expui-theme"
15 | project(":expui-gallery").name = "compose-jetbrains-expui-gallery"
16 |
17 |
--------------------------------------------------------------------------------