diff --git a/README.md b/README.md index da6bc9cf45..f3aa404906 100644 --- a/README.md +++ b/README.md @@ -1,364 +1,7 @@ -[![JetBrains incubator](https://img.shields.io/badge/JetBrains-incubator-yellow?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIuMDAwMDEiIHZpZXdCb3g9IjAgMCAzMiAzMi4wMDAwMSIgd2lkdGg9IjMyIj48c2NyaXB0IHhtbG5zPSIiLz48cGF0aCBkPSJtMCAwaDMydjMyLjAwMDAxaC0zMnoiLz48cGF0aCBkPSJtNCAyNi4wMDAwMWgxMnYyaC0xMnoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=)](https://github.com/JetBrains#jetbrains-on-github) [![CI checks](https://img.shields.io/github/actions/workflow/status/JetBrains/jewel/build.yml?logo=github)](https://github.com/JetBrains/jewel/actions/workflows/build.yml) [![Licensed under Apache 2.0](https://img.shields.io/github/license/JetBrains/jewel?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI0ZGRiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0ibTMgNiAzIDFtMCAwLTMgOWE1LjAwMiA1LjAwMiAwIDAgMCA2LjAwMSAwTTYgN2wzIDlNNiA3bDYtMm02IDIgMy0xbS0zIDEtMyA5YTUuMDAyIDUuMDAyIDAgMCAwIDYuMDAxIDBNMTggN2wzIDltLTMtOS02LTJtMC0ydjJtMCAxNlY1bTAgMTZIOW0zIDBoMyIvPjwvc3ZnPg==)](https://github.com/JetBrains/jewel/blob/main/LICENSE) [![Latest release](https://img.shields.io/github/v/release/JetBrains/jewel?include_prereleases&label=Latest%20Release&logo=github)](https://github.com/JetBrains/jewel/releases/latest) ![Compose for Desktop version](https://img.shields.io/badge/Compose%20for%20Desktop-1.6.0-dev1369?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB2aWV3Qm94PSIwIDAgNjcgNzQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0zNS45OTkgMi42NjNhNS4wMSA1LjAxIDAgMCAwLTQuOTk4IDBsLTI2LjUgMTUuMjUzYTQuOTk0IDQuOTk0IDAgMCAwLTEuMTk4Ljk2MmwxMS4xMDggNi4zNjZjLjI2OC0uMjkuNTgtLjU0LjkzMS0uNzQ0bDE2LjE1Ni05LjM0MmE0IDQgMCAwIDEgNC4wMDQgMEw1MS42NTcgMjQuNWMuMzUxLjIwMy42NjQuNDU1LjkzMi43NDRsMTEuMTA4LTYuMzY2YTQuOTkxIDQuOTkxIDAgMCAwLTEuMTk4LS45NjJsLTI2LjUtMTUuMjUzWm0yOC43MjMgMTcuOTMzLTExLjE4MyA2LjQwOGMuMDc2LjMxLjExNi42MzIuMTE2Ljk1OXYxNy43OTRhNCA0IDAgMCAxLTEuOTU4IDMuNDRsLTE2LjIzNSA5LjYzOGEzLjk5OCAzLjk5OCAwIDAgMS0uOTYyLjQxMnYxMi42M2E1LjAwNSA1LjAwNSAwIDAgMCAxLjQyOC0uNTY5bDI2LjYyLTE1LjczQTQuOTg2IDQuOTg2IDAgMCAwIDY1IDUxLjI4NFYyMi4yMzdjMC0uNTY3LS4wOTctMS4xMi0uMjc4LTEuNjRaTTIgMjIuMjM3YzAtLjU2Ny4wOTctMS4xMi4yNzgtMS42NGwxMS4xODMgNi40MDdjLS4wNzYuMzEtLjExNi42MzItLjExNi45NTl2MTguNjMzYTQgNCAwIDAgMCAyLjA4IDMuNTA5bDE2LjA3NCA4LjhjLjMyLjE3NC42NTYuMzAyIDEuMDAxLjM4NHYxMi42MzhhNS4wMDUgNS4wMDUgMCAwIDEtMS41MTctLjUzM0w0LjYwMyA1Ny4wMkE0Ljk4NyA0Ljk4NyAwIDAgMSAyIDUyLjY0MlYyMi4yMzdaTTMwLjAwMi45MzVhNy4wMTQgNy4wMTQgMCAwIDEgNi45OTYgMGwyNi41IDE1LjI1M0E2Ljk4IDYuOTggMCAwIDEgNjcgMjIuMjM4djI5LjA0N2E2Ljk4IDYuOTggMCAwIDEtMy40MzMgNi4wMDlsLTI2LjYyIDE1LjczMWE3LjAxNCA3LjAxNCAwIDAgMS02LjkyMy4xMkwzLjY0NCA1OC43NzFBNi45ODEgNi45ODEgMCAwIDEgMCA1Mi42NDFWMjIuMjM4YTYuOTggNi45OCAwIDAgMSAzLjUwMi02LjA1TDMwLjAwMi45MzZabS04LjYwNCAyNy41NTIgMTAuNTgyLTYuMTFjLjk0LS41NDIgMi4xLS41NDIgMy4wNCAwbDEwLjU4MiA2LjExYTIuOTk2IDIuOTk2IDAgMCAxIDEuNTAzIDIuNTkzdjExLjY1M2MwIDEuMDU2LS41NiAyLjAzNC0xLjQ3MyAyLjU3NmwtMTAuNjQzIDYuMzA4YTMuMDQ0IDMuMDQ0IDAgMCAxLTMuMDA5LjA1MmwtMTAuNTItNS43NWEyLjk5NiAyLjk5NiAwIDAgMS0xLjU2NS0yLjYyN1YzMS4wOGMwLTEuMDY4LjU3My0yLjA1NiAxLjUwMy0yLjU5M1oiIGZpbGw9IiNmZmYiLz48L3N2Zz4%3D) - # Jewel: a Compose for Desktop theme Jewel logo -Jewel aims at recreating the IntelliJ Platform's _New UI_ Swing Look and Feel in Compose for Desktop, providing a -desktop-optimized theme and set of components. - -> [!WARNING] -> -> This project is in active development, and caution is advised when considering it for production uses. You _can_ use -> it, but you should expect APIs to change often, things to move around and/or break, and all that jazz. Binary -> compatibility is not guaranteed across releases, and APIs are still in flux and subject to change. -> -> Writing 3rd party IntelliJ Plugins in Compose for Desktop is currently **not officially supported** by the IntelliJ -> Platform. It should work, but your mileage may vary, and if things break you're on your own. -> -> Use at your own risk! - -Jewel provides an implementation of the IntelliJ Platform themes that can be used in any Compose for Desktop -application. Additionally, it has a Swing LaF Bridge that only works in the IntelliJ Platform (i.e., used to create IDE -plugins), but automatically mirrors the current Swing LaF into Compose for a native-looking, consistent UI. - -## Getting started - -To use Jewel in your app, you only need to add the relevant dependency. There are two scenarios: standalone Compose for -Desktop app, and IntelliJ Platform plugin. - -For now, Jewel artifacts aren't available on Maven Central. You need to add a custom Maven repository to your build: - -```kotlin -repositories { - maven("https://packages.jetbrains.team/maven/p/kpm/public/") - // Any other repositories you need (e.g., mavenCentral()) -} -``` - -If you're writing a **standalone app**, then you should depend on the `int-ui-standalone` artifact: - -```kotlin -dependencies { - implementation("org.jetbrains.jewel:jewel-int-ui-standalone:[jewel version]") - - // Optional, for custom decorated windows: - implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window:[jewel version]") -} -``` - -For an **IntelliJ Platform plugin**, then you should depend on the appropriate `ide-laf-bridge` artifact: - -```kotlin -dependencies { - // The platform version is a supported major IJP version (e.g., 232 or 233 for 2023.2 and 2023.3 respectively) - implementation("org.jetbrains.jewel:jewel-ide-laf-bridge-[platform version]:[jewel version]") -} -``` - -
- -> [!TIP] -> -> -> -> -> If you want to learn more about Jewel and Compose for Desktop and why they're a great, modern solution for your -> desktop -> UI needs, check out [this talk](https://www.droidcon.com/2023/11/15/meet-jewelcreate-ide-plugins-in-compose/) by Jewel -> contributors Sebastiano and Chris. -> -> It covers why Compose is a viable choice, and an overview of the Jewel project, plus -> some real-life use cases.
- -
- -## Project structure - -The project is split in modules: - -1. `buildSrc` contains the build logic, including: - * The `jewel` and `jewel-publish` configuration plugins - * The `jewel-check-public-api` and `jewel-linting` configuration plugins - * The Theme Palette generator plugin - * The Studio Releases generator plugin -2. `foundation` contains the foundational Jewel functionality: - * Basic components without strong styling (e.g., `SelectableLazyColumn`, `BasicLazyTree`) - * The `JewelTheme` interface with a few basic composition locals - * The state management primitives - * The Jewel annotations - * A few other primitives -3. `ui` contains all the styled components and custom painters logic -4. `decorated-window` contains basic, unstyled functionality to have custom window decoration on the JetBrains Runtime -5. `int-ui` contains two modules: - * `int-ui-standalone` has a standalone version of the Int UI styling values that can be used in any Compose for - Desktop app - * `int-ui-decorated-window` has a standalone version of the Int UI styling values for the custom window decoration - that can be used in any Compose for Desktop app -6. `ide-laf-bridge` contains the Swing LaF bridge to use in IntelliJ Platform plugins (see more below) -7. `samples` contains the example apps, which showcase the available components: - * `standalone` is a regular CfD app, using the standalone theme definitions and custom window decoration - * `ide-plugin` is an IntelliJ plugin that showcases the use of the Swing Bridge - -## Branching strategy and IJ Platforms - -Code on the main branch is developed and tested against the current latest IntelliJ Platform version. - -When the EAP for a new major version starts, we cut a `releases/xxx` release branch, where `xxx` is the tracked major -IJP version. At that point, the main branch starts tracking the latest available major IJP version, and changes are -cherry-picked into each release branch as needed. All active release branches have the same functionality (where -supported by the corresponding IJP version), but might differ in platform version-specific fixes and internals. - -The standalone Int UI theme will always work the same way as the latest major IJP version; release branches will not -include the `int-ui` module, which is always released from the main branch. - -Releases of Jewel are always cut from a tag on the main branch; the HEAD of each `releases/xxx` branch is then tagged -as `[mainTag]-xxx`, and used to publish the artifacts for that major IJP version. - -> ![IMPORTANT] -> We only support the latest build of IJP for each major IJP version. If the latest 233 version is 2023.3.3, for -> example, we will only guarantee that Jewel works on that. Versions 2023.3.0–2023.3.2 might or might not work. - -### Int UI Standalone theme - -The standalone theme can be used in any Compose for Desktop app. You use it as a normal theme, and you can customise it -to your heart's content. By default, it matches the official Int UI specs. - -For an example on how to set up a standalone app, you can refer to -the [`standalone` sample](samples/standalone/build.gradle.kts). - -> [!WARNING] -> Note that Jewel **requires** the JetBrains Runtime to work correctly. Some features like font loading depend on it, -> as it has extra features and patches for UI functionalities that aren't available in other JDKs. -> We **do not support** running Jewel on any other JDK. - -To use Jewel components in a non-IntelliJ Platform environment, you need to wrap your UI hierarchy in a `IntUiTheme` -composable: - -```kotlin -IntUiTheme(isDark = false) { - // ... -} -``` - -If you want more control over the theming, you can use other `IntUiTheme` overloads, like the standalone sample does. - -#### Custom window decoration - -The JetBrains Runtime allows windows to have a custom decoration instead of the regular title bar. - -![A screenshot of the custom window decoration in the standalone sample](art/docs/custom-chrome.png) - -The standalone sample app shows how to easily get something that looks like a JetBrains IDE; if you want to go _very_ -custom, you only need to depend on the `decorated-window` module, which contains all the required primitives, but not -the Int UI styling. - -To get an IntelliJ-like custom title bar, you need to pass the window decoration styling to your theme call, and add the -`DecoratedWindow` composable at the top level of the theme: - -```kotlin -IntUiTheme( - themeDefinition, - componentStyling = { - themeDefinition.decoratedWindowComponentStyling( - titleBarStyle = TitleBarStyle.light() - ) - }, -) { - DecoratedWindow( - onCloseRequest = { exitApplication() }, - ) { - // ... - } -} -``` - -### Running on the IntelliJ Platform: the Swing bridge - -Jewel includes a crucial element for proper integration with the IDE: a bridge between the Swing components — theme -and LaF — and the Compose world. - -This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ -theme, and apply them to the Compose components as well. This means Jewel will automatically adapt to IntelliJ Platform -themes that use the [standard theming](https://plugins.jetbrains.com/docs/intellij/themes-getting-started.html) -mechanisms. - -> [!NOTE] -> IntelliJ themes that use non-standard mechanisms (such as providing custom UI implementations for Swing components) -> are not, and can never, be supported. - -If you're writing an IntelliJ Platform plugin, you should use the `SwingBridgeTheme` instead of the standalone theme: - -```kotlin -SwingBridgeTheme { - // ... -} -``` - -#### Supported IntelliJ Platform versions - -To use Jewel in the IntelliJ Platform, you should depend on the appropriate `jewel-ide-laf-bridge-*` artifact, which -will bring in the necessary transitive dependencies. These are the currently supported versions of the IntelliJ Platform -and the branch on which the corresponding bridge code lives: - -| IntelliJ Platform version(s) | Branch to use | - |------------------------------|-------------------| -| 2024.1 (EAP 3+) | `main` | -| 2023.3 | `releases/233` | -| 2023.2 | `releases/232` | -| 2023.1 or older | **Not supported** | - -For an example on how to set up an IntelliJ Plugin, you can refer to -the [`ide-plugin` sample](samples/ide-plugin/build.gradle.kts). - -#### Accessing icons - -When you want to draw an icon from the resources, you can either use the `Icon` composable and pass it the resource path -and the corresponding class to look up the classpath from, or go one lever deeper and use the lower level, -`Painter`-based API. - -The `Icon` approach looks like this: - -```kotlin -// Load the "close" icon from the IDE's AllIcons class -Icon( - "actions/close.svg", - iconClass = AllIcons::class.java, - contentDescription = "Close", -) -``` - -To obtain a `Painter`, instead, you'd use: - -```kotlin -val painterProvider = rememberResourcePainterProvider( - path = "actions/close.svg", - iconClass = AllIcons::class.java -) -val painter by painterProvider.getPainter() -``` - -#### Icon runtime patching - -Jewel emulates the under-the-hood machinations that happen in the IntelliJ Platform when loading icons. Specifically, -the resource will be subject to some transformations before being loaded. - -For example, in the IDE, if New UI is active, the icon path may be replaced with a different one. Some key colors in SVG -icons will also be replaced based on the current theme. See -[the docs](https://plugins.jetbrains.com/docs/intellij/work-with-icons-and-images.html#new-ui-icons). - -Beyond that, even in standalone, Jewel will pick up icons with the appropriate dark/light variant for the current theme, -and for bitmap icons it will try to pick the 2x variants based on the `LocalDensity`. - -If you have a _stateful_ icon, that is if you need to display different icons based on some state, you can use the -`PainterProvider.getPainter(PainterHint...)` overload. You can then use one of the state-mapping `PainterHint` to let -Jewel load the appropriate icon automatically: - -```kotlin -// myState implements SelectableComponentState and has a ToggleableState property -val myPainter by myPainterProvider.getPainter( - if (myState.toggleableState == ToggleableState.Indeterminate) { - IndeterminateHint - } else { - PainterHint.None - }, - Selected(myState), - Stateful(myState), -) -``` - -Where the `IndeterminateHint` looks like this: - -```kotlin -private object IndeterminateHint : PainterSuffixHint() { - override fun suffix(): String = "Indeterminate" -} -``` - -Assuming the PainterProvider has a base path of `components/myIcon.svg`, Jewel will automatically translate it to the -right path based on the state. If you want to learn more about this system, look at the `PainterHint` interface and its -implementations. - -### Fonts - -To load a system font, you can obtain it by its family name: - -```kotlin -val myFamily = FontFamily("My Family") -``` - -If you want to use a font embedded in the JetBrains Runtime, you can use the `EmbeddedFontFamily` API instead: - -```kotlin -import javax.swing.text.StyledEditorKit.FontFamilyAction - -// Will return null if no matching font family exists in the JBR -val myEmbeddedFamily = EmbeddedFontFamily("Embedded family") - -// It's recommended to load a fallback family when dealing with embedded familes -val myFamily = myEmbeddedFamily ?: FontFamily("Fallback family") -``` - -You can obtain a `FontFamily` from any `java.awt.Font` — including from `JBFont`s — by using the `asComposeFontFamily()` -API: - -```kotlin -val myAwtFamily = myFont.asComposeFontFamily() - -// This will attempt to resolve the logical AWT font -val myLogicalFamily = Font("Dialog").asComposeFontFamily() - -// This only works in the IntelliJ Platform, -// since JBFont is only available there -val myLabelFamily = JBFont.label().asComposeFontFamily() -``` - -### Swing interoperability - -As this is Compose for Desktop, you get a good degree of interoperability with Swing. To avoid glitches and z-order -issues, you should enable the -[experimental Swing rendering pipeline](https://blog.jetbrains.com/kotlin/2023/08/compose-multiplatform-1-5-0-release/#enhanced-swing-interop) -before you initialize Compose content. - -The `ToolWindow.addComposeTab()` extension function provided by the `ide-laf-bridge` module will take care of that for -you. However, if you want to also enable it in other scenarios and in standalone applications, you can call the -`enableNewSwingCompositing()` function in your Compose entry points (that is, right before creating a `ComposePanel`). - -> [!NOTE] -> The new Swing rendering pipeline is experimental and may have performance repercussions when using infinitely -> repeating animations. This is a known issue by the Compose Multiplatform team, that requires changes in the Java -> runtime to fix. Once the required changes are made in the JetBrains Runtime, we'll remove this notice. - -## Written with Jewel - -Here is a small selection of projects that use Compose for Desktop and Jewel: - -* [Package Search](https://github.com/JetBrains/package-search-intellij-plugin) (IntelliJ Platform plugin) -* [Kotlin Explorer](https://github.com/romainguy/kotlin-explorer) (standalone app) -* ...and more to come! - -## Need help? - -You can find help on the [`#jewel`](https://app.slack.com/client/T09229ZC6/C05T8U2C31T) channel on the Kotlin Slack. -If you don't already have access to the Kotlin Slack, you can request it -[here](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up). - -## License - -Jewel is licensed under the [Apache 2.0 license](https://github.com/JetBrains/jewel/blob/main/LICENSE). - -``` -Copyright 2022–4 JetBrains s.r.o. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 +Please refer to the readme file on the `main` branch for further information. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -``` +**This is the build for the IntelliJ Platform 23.2** diff --git a/build.gradle.kts b/build.gradle.kts index d698907af1..1db96e8c93 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,13 +4,9 @@ plugins { } dependencies { - sarif(projects.decoratedWindow) sarif(projects.foundation) sarif(projects.ideLafBridge) - sarif(projects.intUi.intUiDecoratedWindow) - sarif(projects.intUi.intUiStandalone) sarif(projects.samples.idePlugin) - sarif(projects.samples.standalone) sarif(projects.ui) } diff --git a/decorated-window/api/decorated-window.api b/decorated-window/api/decorated-window.api deleted file mode 100644 index 7e0cdd28f9..0000000000 --- a/decorated-window/api/decorated-window.api +++ /dev/null @@ -1,252 +0,0 @@ -public abstract interface class com/jetbrains/DesktopActions { - public abstract fun setHandler (Lcom/jetbrains/DesktopActions$Handler;)V -} - -public abstract interface class com/jetbrains/DesktopActions$Handler { - public fun browse (Ljava/net/URI;)V - public fun edit (Ljava/io/File;)V - public fun mail (Ljava/net/URI;)V - public fun open (Ljava/io/File;)V - public fun print (Ljava/io/File;)V -} - -public class com/jetbrains/JBR { - public static fun getApiVersion ()Ljava/lang/String; - public static fun getDesktopActions ()Lcom/jetbrains/DesktopActions; - public static fun getRoundedCornersManager ()Lcom/jetbrains/RoundedCornersManager; - public static fun getWindowDecorations ()Lcom/jetbrains/WindowDecorations; - public static fun getWindowMove ()Lcom/jetbrains/WindowMove; - public static fun isAvailable ()Z - public static fun isDesktopActionsSupported ()Z - public static fun isRoundedCornersManagerSupported ()Z - public static fun isWindowDecorationsSupported ()Z - public static fun isWindowMoveSupported ()Z -} - -public abstract interface class com/jetbrains/RoundedCornersManager { - public abstract fun setRoundedCorners (Ljava/awt/Window;Ljava/lang/Object;)V -} - -public abstract interface class com/jetbrains/WindowDecorations { - public abstract fun createCustomTitleBar ()Lcom/jetbrains/WindowDecorations$CustomTitleBar; - public abstract fun setCustomTitleBar (Ljava/awt/Dialog;Lcom/jetbrains/WindowDecorations$CustomTitleBar;)V - public abstract fun setCustomTitleBar (Ljava/awt/Frame;Lcom/jetbrains/WindowDecorations$CustomTitleBar;)V -} - -public abstract interface class com/jetbrains/WindowDecorations$CustomTitleBar { - public abstract fun forceHitTest (Z)V - public abstract fun getContainingWindow ()Ljava/awt/Window; - public abstract fun getHeight ()F - public abstract fun getLeftInset ()F - public abstract fun getProperties ()Ljava/util/Map; - public abstract fun getRightInset ()F - public abstract fun putProperties (Ljava/util/Map;)V - public abstract fun putProperty (Ljava/lang/String;Ljava/lang/Object;)V - public abstract fun setHeight (F)V -} - -public abstract interface class com/jetbrains/WindowMove { - public abstract fun startMovingTogetherWithMouse (Ljava/awt/Window;I)V -} - -public final class org/jetbrains/jewel/window/DecoratedWindowKt { - public static final fun DecoratedWindow (Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/window/WindowState;ZLjava/lang/String;Landroidx/compose/ui/graphics/painter/Painter;ZZZZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/window/styling/DecoratedWindowStyle;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V -} - -public abstract interface class org/jetbrains/jewel/window/DecoratedWindowScope : androidx/compose/ui/window/FrameWindowScope { - public abstract fun getState-VA8cQZQ ()J - public abstract fun getWindow ()Landroidx/compose/ui/awt/ComposeWindow; -} - -public final class org/jetbrains/jewel/window/DecoratedWindowState { - public static final field Companion Lorg/jetbrains/jewel/window/DecoratedWindowState$Companion; - public static final synthetic fun box-impl (J)Lorg/jetbrains/jewel/window/DecoratedWindowState; - public static fun constructor-impl (J)J - public static final fun copy-zAQEbgo (JZZZZ)J - public static synthetic fun copy-zAQEbgo$default (JZZZZILjava/lang/Object;)J - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (JLjava/lang/Object;)Z - public static final fun equals-impl0 (JJ)Z - public final fun getState-s-VKNKU ()J - public fun hashCode ()I - public static fun hashCode-impl (J)I - public static final fun isActive-impl (J)Z - public static final fun isFullscreen-impl (J)Z - public static final fun isMaximized-impl (J)Z - public static final fun isMinimized-impl (J)Z - public fun toString ()Ljava/lang/String; - public static fun toString-impl (J)Ljava/lang/String; - public final synthetic fun unbox-impl ()J -} - -public final class org/jetbrains/jewel/window/DecoratedWindowState$Companion { - public final fun getActive-s-VKNKU ()J - public final fun getFullscreen-s-VKNKU ()J - public final fun getMaximize-s-VKNKU ()J - public final fun getMinimize-s-VKNKU ()J - public final fun of-LPCgXDc (Landroidx/compose/ui/awt/ComposeWindow;)J - public final fun of-zAQEbgo (ZZZZ)J - public static synthetic fun of-zAQEbgo$default (Lorg/jetbrains/jewel/window/DecoratedWindowState$Companion;ZZZZILjava/lang/Object;)J -} - -public final class org/jetbrains/jewel/window/ThemeKt { - public static final fun getDefaultDecoratedWindowStyle (Lorg/jetbrains/jewel/foundation/theme/JewelTheme$Companion;Landroidx/compose/runtime/Composer;I)Lorg/jetbrains/jewel/window/styling/DecoratedWindowStyle; - public static final fun getDefaultTitleBarStyle (Lorg/jetbrains/jewel/foundation/theme/JewelTheme$Companion;Landroidx/compose/runtime/Composer;I)Lorg/jetbrains/jewel/window/styling/TitleBarStyle; -} - -public final class org/jetbrains/jewel/window/TitleBarKt { - public static final fun TitleBar-T042LqI (Lorg/jetbrains/jewel/window/DecoratedWindowScope;Landroidx/compose/ui/Modifier;JLorg/jetbrains/jewel/window/styling/TitleBarStyle;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V -} - -public abstract interface class org/jetbrains/jewel/window/TitleBarScope { - public abstract fun align (Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment$Horizontal;)Landroidx/compose/ui/Modifier; - public abstract fun getIcon ()Landroidx/compose/ui/graphics/painter/Painter; - public abstract fun getTitle ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/TitleBar_MacOSKt { - public static final fun newFullscreenControls (Landroidx/compose/ui/Modifier;Z)Landroidx/compose/ui/Modifier; - public static synthetic fun newFullscreenControls$default (Landroidx/compose/ui/Modifier;ZILjava/lang/Object;)Landroidx/compose/ui/Modifier; -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowColors { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/DecoratedWindowColors$Companion; - public synthetic fun (JJLkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun borderFor-3hEOMOc (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; - public fun equals (Ljava/lang/Object;)Z - public final fun getBorder-0d7_KjU ()J - public final fun getBorderInactive-0d7_KjU ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowColors$Companion { -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowMetrics { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/DecoratedWindowMetrics$Companion; - public synthetic fun (FLkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getBorderWidth-D9Ej5fM ()F - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowMetrics$Companion { -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowStyle { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/DecoratedWindowStyle$Companion; - public fun (Lorg/jetbrains/jewel/window/styling/DecoratedWindowColors;Lorg/jetbrains/jewel/window/styling/DecoratedWindowMetrics;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getColors ()Lorg/jetbrains/jewel/window/styling/DecoratedWindowColors; - public final fun getMetrics ()Lorg/jetbrains/jewel/window/styling/DecoratedWindowMetrics; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowStyle$Companion { -} - -public final class org/jetbrains/jewel/window/styling/DecoratedWindowStylingKt { - public static final fun getLocalDecoratedWindowStyle ()Landroidx/compose/runtime/ProvidableCompositionLocal; -} - -public final class org/jetbrains/jewel/window/styling/TitleBarColors { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/TitleBarColors$Companion; - public synthetic fun (JJJJJJJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun backgroundFor-3hEOMOc (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; - public fun equals (Ljava/lang/Object;)Z - public final fun getBackground-0d7_KjU ()J - public final fun getBorder-0d7_KjU ()J - public final fun getContent-0d7_KjU ()J - public final fun getDropdownHoveredBackground-0d7_KjU ()J - public final fun getDropdownPressedBackground-0d7_KjU ()J - public final fun getFullscreenControlButtonsBackground-0d7_KjU ()J - public final fun getIconButtonHoveredBackground-0d7_KjU ()J - public final fun getIconButtonPressedBackground-0d7_KjU ()J - public final fun getInactiveBackground-0d7_KjU ()J - public final fun getTitlePaneButtonHoveredBackground-0d7_KjU ()J - public final fun getTitlePaneButtonPressedBackground-0d7_KjU ()J - public final fun getTitlePaneCloseButtonHoveredBackground-0d7_KjU ()J - public final fun getTitlePaneCloseButtonPressedBackground-0d7_KjU ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/TitleBarColors$Companion { -} - -public final class org/jetbrains/jewel/window/styling/TitleBarIcons { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion; - public fun (Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getCloseButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider; - public final fun getMaximizeButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider; - public final fun getMinimizeButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider; - public final fun getRestoreButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/TitleBarIcons$Companion { -} - -public final class org/jetbrains/jewel/window/styling/TitleBarMetrics { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/TitleBarMetrics$Companion; - public synthetic fun (FFFJLkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getGradientEndX-D9Ej5fM ()F - public final fun getGradientStartX-D9Ej5fM ()F - public final fun getHeight-D9Ej5fM ()F - public final fun getTitlePaneButtonSize-MYxV2XQ ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/TitleBarMetrics$Companion { -} - -public final class org/jetbrains/jewel/window/styling/TitleBarStyle { - public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/window/styling/TitleBarStyle$Companion; - public fun (Lorg/jetbrains/jewel/window/styling/TitleBarColors;Lorg/jetbrains/jewel/window/styling/TitleBarMetrics;Lorg/jetbrains/jewel/window/styling/TitleBarIcons;Lorg/jetbrains/jewel/ui/component/styling/DropdownStyle;Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getColors ()Lorg/jetbrains/jewel/window/styling/TitleBarColors; - public final fun getDropdownStyle ()Lorg/jetbrains/jewel/ui/component/styling/DropdownStyle; - public final fun getIconButtonStyle ()Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle; - public final fun getIcons ()Lorg/jetbrains/jewel/window/styling/TitleBarIcons; - public final fun getMetrics ()Lorg/jetbrains/jewel/window/styling/TitleBarMetrics; - public final fun getPaneButtonStyle ()Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle; - public final fun getPaneCloseButtonStyle ()Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/jetbrains/jewel/window/styling/TitleBarStyle$Companion { -} - -public final class org/jetbrains/jewel/window/styling/TitleBarStylingKt { - public static final fun getLocalTitleBarStyle ()Landroidx/compose/runtime/ProvidableCompositionLocal; -} - -public final class org/jetbrains/jewel/window/utils/DesktopPlatform : java/lang/Enum { - public static final field Companion Lorg/jetbrains/jewel/window/utils/DesktopPlatform$Companion; - public static final field Linux Lorg/jetbrains/jewel/window/utils/DesktopPlatform; - public static final field MacOS Lorg/jetbrains/jewel/window/utils/DesktopPlatform; - public static final field Unknown Lorg/jetbrains/jewel/window/utils/DesktopPlatform; - public static final field Windows Lorg/jetbrains/jewel/window/utils/DesktopPlatform; - public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/jewel/window/utils/DesktopPlatform; - public static fun values ()[Lorg/jetbrains/jewel/window/utils/DesktopPlatform; -} - -public final class org/jetbrains/jewel/window/utils/DesktopPlatform$Companion { - public final fun getCurrent ()Lorg/jetbrains/jewel/window/utils/DesktopPlatform; -} - diff --git a/decorated-window/build.gradle.kts b/decorated-window/build.gradle.kts deleted file mode 100644 index 02e83d703a..0000000000 --- a/decorated-window/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -import org.jetbrains.compose.ComposeBuildConfig - -plugins { - jewel - `jewel-publish` - `jewel-check-public-api` - alias(libs.plugins.composeDesktop) -} - -private val composeVersion - get() = ComposeBuildConfig.composeVersion - -dependencies { - api("org.jetbrains.compose.foundation:foundation-desktop:$composeVersion") - api(projects.ui) - implementation(libs.jna.core) -} diff --git a/decorated-window/src/main/java/com/jetbrains/DesktopActions.java b/decorated-window/src/main/java/com/jetbrains/DesktopActions.java deleted file mode 100644 index f2efcb477a..0000000000 --- a/decorated-window/src/main/java/com/jetbrains/DesktopActions.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2022 JetBrains s.r.o. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.jetbrains; - -import java.io.File; -import java.io.IOException; -import java.net.URI; - -public interface DesktopActions { - - void setHandler(Handler handler); - - interface Handler { - default void open(File file) throws IOException { throw new UnsupportedOperationException(); } - default void edit(File file) throws IOException { throw new UnsupportedOperationException(); } - default void print(File file) throws IOException { throw new UnsupportedOperationException(); } - default void mail(URI mailtoURL) throws IOException { throw new UnsupportedOperationException(); } - default void browse(URI uri) throws IOException { throw new UnsupportedOperationException(); } - } - -} diff --git a/decorated-window/src/main/java/com/jetbrains/JBR.java b/decorated-window/src/main/java/com/jetbrains/JBR.java deleted file mode 100644 index 0f5f04505e..0000000000 --- a/decorated-window/src/main/java/com/jetbrains/JBR.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.jetbrains; - -import java.lang.invoke.MethodHandles; -import java.lang.reflect.InvocationTargetException; - -/** - * This class is an entry point into JBR API. - * JBR API is a collection of services, classes, interfaces, etc., - * which require tight interaction with JRE and therefore are implemented inside JBR. - *
JBR API consists of two parts:
- *
    - *
  • Client side - {@code jetbrains.api} module, mostly containing interfaces
  • - *
  • JBR side - actual implementation code inside JBR
  • - *
- * Client and JBR side are linked dynamically at runtime and do not have to be of the same version. - * In some cases (e.g. running on different JRE or old JBR) system will not be able to find - * implementation for some services, so you'll need a fallback behavior for that case. - *

Simple usage example:

- *
{@code
- * if (JBR.isSomeServiceSupported()) {
- *     JBR.getSomeService().doSomething();
- * } else {
- *     planB();
- * }
- * }
- * - * @implNote JBR API is initialized on first access to this class (in static initializer). - * Actual implementation is linked on demand, when corresponding service is requested by client. - */ -public class JBR { - - private static final ServiceApi api; - private static final Exception bootstrapException; - - static { - ServiceApi a = null; - Exception exception = null; - try { - a = (ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap") - .getMethod("bootstrap", MethodHandles.Lookup.class) - .invoke(null, MethodHandles.lookup()); - } catch (InvocationTargetException e) { - Throwable t = e.getCause(); - if (t instanceof Error error) throw error; - else throw new Error(t); - } catch (IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { - exception = e; - } - api = a; - bootstrapException = exception; - } - - private JBR() { - } - - private static T getService(Class interFace, FallbackSupplier fallback) { - T service = getService(interFace); - try { - return service != null ? service : fallback != null ? fallback.get() : null; - } catch (Throwable ignore) { - return null; - } - } - - static T getService(Class interFace) { - return api == null ? null : api.getService(interFace); - } - - /** - * @return true when running on JBR which implements JBR API - */ - public static boolean isAvailable() { - return api != null; - } - - /** - * @return JBR API version in form {@code JBR.MAJOR.MINOR.PATCH} - * @implNote This is an API version, which comes with client application, - * it has nothing to do with JRE it runs on. - */ - public static String getApiVersion() { - return "17.0.8.1b1070.2.1.9.0"; - } - - /** - * Internal API interface, contains most basic methods for communication between client and JBR. - */ - private interface ServiceApi { - - T getService(Class interFace); - } - - @FunctionalInterface - private interface FallbackSupplier { - T get() throws Throwable; - } - - // ========================== Generated metadata ========================== - - /** - * Generated client-side metadata, needed by JBR when linking the implementation. - */ - private static final class Metadata { - private static final String[] KNOWN_SERVICES = {"com.jetbrains.ExtendedGlyphCache", "com.jetbrains.DesktopActions", "com.jetbrains.CustomWindowDecoration", "com.jetbrains.ProjectorUtils", "com.jetbrains.FontExtensions", "com.jetbrains.RoundedCornersManager", "com.jetbrains.GraphicsUtils", "com.jetbrains.WindowDecorations", "com.jetbrains.JBRFileDialogService", "com.jetbrains.AccessibleAnnouncer", "com.jetbrains.JBR$ServiceApi", "com.jetbrains.Jstack", "com.jetbrains.WindowMove"}; - private static final String[] KNOWN_PROXIES = {"com.jetbrains.JBRFileDialog", "com.jetbrains.WindowDecorations$CustomTitleBar"}; - } - - // ======================= Generated static methods ======================= - - private static class DesktopActions__Holder { - private static final DesktopActions INSTANCE = getService(DesktopActions.class, null); - } - - /** - * @return true if current runtime has implementation for all methods in {@link DesktopActions} - * and its dependencies (can fully implement given service). - * @see #getDesktopActions() - */ - public static boolean isDesktopActionsSupported() { - return DesktopActions__Holder.INSTANCE != null; - } - - /** - * @return full implementation of {@link DesktopActions} service if any, or {@code null} otherwise - */ - public static DesktopActions getDesktopActions() { - return DesktopActions__Holder.INSTANCE; - } - - private static class RoundedCornersManager__Holder { - private static final RoundedCornersManager INSTANCE = getService(RoundedCornersManager.class, null); - } - - /** - * @return true if current runtime has implementation for all methods in {@link RoundedCornersManager} - * and its dependencies (can fully implement given service). - * @see #getRoundedCornersManager() - */ - public static boolean isRoundedCornersManagerSupported() { - return RoundedCornersManager__Holder.INSTANCE != null; - } - - /** - * This manager allows decorate awt Window with rounded corners. - * Appearance depends from operating system. - * - * @return full implementation of {@link RoundedCornersManager} service if any, or {@code null} otherwise - */ - public static RoundedCornersManager getRoundedCornersManager() { - return RoundedCornersManager__Holder.INSTANCE; - } - - private static class WindowDecorations__Holder { - private static final WindowDecorations INSTANCE = getService(WindowDecorations.class, null); - } - - /** - * @return true if current runtime has implementation for all methods in {@link WindowDecorations} - * and its dependencies (can fully implement given service). - * @see #getWindowDecorations() - */ - public static boolean isWindowDecorationsSupported() { - return WindowDecorations__Holder.INSTANCE != null; - } - - /** - * Window decorations consist of title bar, window controls and border. - * - * @return full implementation of {@link WindowDecorations} service if any, or {@code null} otherwise - * @see WindowDecorations.CustomTitleBar - */ - public static WindowDecorations getWindowDecorations() { - return WindowDecorations__Holder.INSTANCE; - } - - private static class WindowMove__Holder { - private static final WindowMove INSTANCE = getService(WindowMove.class, null); - } - - /** - * @return true if current runtime has implementation for all methods in {@link WindowMove} - * and its dependencies (can fully implement given service). - * @see #getWindowMove() - */ - public static boolean isWindowMoveSupported() { - return WindowMove__Holder.INSTANCE != null; - } - - /** - * @return full implementation of {@link WindowMove} service if any, or {@code null} otherwise - */ - public static WindowMove getWindowMove() { - return WindowMove__Holder.INSTANCE; - } -} diff --git a/decorated-window/src/main/java/com/jetbrains/RoundedCornersManager.java b/decorated-window/src/main/java/com/jetbrains/RoundedCornersManager.java deleted file mode 100644 index 424e03feda..0000000000 --- a/decorated-window/src/main/java/com/jetbrains/RoundedCornersManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.jetbrains; - -import java.awt.*; - -/** - * This manager allows decorate awt Window with rounded corners. - * Appearance depends from operating system. - */ -public interface RoundedCornersManager { - /** - * @param params for macOS is Float object with radius or - * Array with {Float for radius, Integer for border width, java.awt.Color for border color}. - * - * @param params for Windows 11 is String with values: - * "default" - let the system decide whether or not to round window corners, - * "none" - never round window corners, - * "full" - round the corners if appropriate, - * "small" - round the corners if appropriate, with a small radius. - */ - void setRoundedCorners(Window window, Object params); -} diff --git a/decorated-window/src/main/java/com/jetbrains/WindowDecorations.java b/decorated-window/src/main/java/com/jetbrains/WindowDecorations.java deleted file mode 100644 index fc0144fa7c..0000000000 --- a/decorated-window/src/main/java/com/jetbrains/WindowDecorations.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.jetbrains; - -import java.awt.*; -import java.util.Map; - -/** - * Window decorations consist of title bar, window controls and border. - * @see CustomTitleBar - */ -public interface WindowDecorations { - - /** - * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the - * top of the frame with window controls painted over the client area. - * {@code customTitleBar=null} resets to the default appearance with system-provided title bar. - * @see CustomTitleBar - * @see #createCustomTitleBar() - */ - void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar); - - /** - * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the - * top of the dialog with window controls painted over the client area. - * {@code customTitleBar=null} resets to the default appearance with system-provided title bar. - * @see CustomTitleBar - * @see #createCustomTitleBar() - */ - void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar); - - /** - * You must {@linkplain CustomTitleBar#setHeight(float) set title bar height} before adding it to a window. - * @see CustomTitleBar - * @see #setCustomTitleBar(Frame, CustomTitleBar) - * @see #setCustomTitleBar(Dialog, CustomTitleBar) - */ - CustomTitleBar createCustomTitleBar(); - - /** - * Custom title bar allows merging of window content with native title bar, - * which is done by treating title bar as part of client area, but with some - * special behavior like dragging or maximizing on double click. - * Custom title bar has {@linkplain CustomTitleBar#getHeight() height} and controls. - * @implNote Behavior is platform-dependent, only macOS and Windows are supported. - * @see #setCustomTitleBar(Frame, CustomTitleBar) - */ - interface CustomTitleBar { - - /** - * @return title bar height, measured in pixels from the top of client area, i.e. excluding top frame border. - */ - float getHeight(); - - /** - * @param height title bar height, measured in pixels from the top of client area, - * i.e. excluding top frame border. Must be > 0. - */ - void setHeight(float height); - - /** - * @see #putProperty(String, Object) - */ - Map getProperties(); - - /** - * @see #putProperty(String, Object) - */ - void putProperties(Map m); - - /** - * Windows & macOS properties: - *
    - *
  • {@code controls.visible} : {@link Boolean} - whether title bar controls - * (minimize/maximize/close buttons) are visible, default = true.
  • - *
- * Windows properties: - *
    - *
  • {@code controls.width} : {@link Number} - width of block of buttons (not individual buttons). - * Note that dialogs have only one button, while frames usually have 3 of them.
  • - *
  • {@code controls.dark} : {@link Boolean} - whether to use dark or light color theme - * (light or dark icons respectively).
  • - *
  • {@code controls..} : {@link Color} - precise control over button colors, - * where {@code } is one of: - *
    • {@code foreground}
    • {@code background}
    - * and {@code } is one of: - *
      - *
    • {@code normal}
    • - *
    • {@code hovered}
    • - *
    • {@code pressed}
    • - *
    • {@code disabled}
    • - *
    • {@code inactive}
    • - *
    - *
- */ - void putProperty(String key, Object value); - - /** - * @return space occupied by title bar controls on the left (px) - */ - float getLeftInset(); - /** - * @return space occupied by title bar controls on the right (px) - */ - float getRightInset(); - - /** - * By default, any component which has no cursor or mouse event listeners set is considered transparent for - * native title bar actions. That is, dragging simple JPanel in title bar area will drag the - * window, but dragging a JButton will not. Adding mouse listener to a component will prevent any native actions - * inside bounds of that component. - *

- * This method gives you precise control of whether to allow native title bar actions or not. - *

    - *
  • {@code client=true} means that mouse is currently over a client area. Native title bar behavior is disabled.
  • - *
  • {@code client=false} means that mouse is currently over a non-client area. Native title bar behavior is enabled.
  • - *
- * Intended usage: - *
    - *
  • This method must be called in response to all {@linkplain java.awt.event.MouseEvent mouse events} - * except {@link java.awt.event.MouseEvent#MOUSE_EXITED} and {@link java.awt.event.MouseEvent#MOUSE_WHEEL}.
  • - *
  • This method is called per-event, i.e. when component has multiple listeners, you only need to call it once.
  • - *
  • If this method hadn't been called, title bar behavior is reverted back to default upon processing the event.
  • - *
- * Note that hit test value is relevant only for title bar area, e.g. calling - * {@code forceHitTest(false)} will not make window draggable via non-title bar area. - * - *

Example:

- * Suppose you have a {@code JPanel} in the title bar area. You want it to respond to right-click for - * some popup menu, but also retain native drag and double-click behavior. - *
-         *     CustomTitleBar titlebar = ...;
-         *     JPanel panel = ...;
-         *     MouseAdapter adapter = new MouseAdapter() {
-         *         private void hit() { titlebar.forceHitTest(false); }
-         *         public void mouseClicked(MouseEvent e) {
-         *             hit();
-         *             if (e.getButton() == MouseEvent.BUTTON3) ...;
-         *         }
-         *         public void mousePressed(MouseEvent e) { hit(); }
-         *         public void mouseReleased(MouseEvent e) { hit(); }
-         *         public void mouseEntered(MouseEvent e) { hit(); }
-         *         public void mouseDragged(MouseEvent e) { hit(); }
-         *         public void mouseMoved(MouseEvent e) { hit(); }
-         *     };
-         *     panel.addMouseListener(adapter);
-         *     panel.addMouseMotionListener(adapter);
-         * 
- */ - void forceHitTest(boolean client); - - Window getContainingWindow(); - } -} diff --git a/decorated-window/src/main/java/com/jetbrains/WindowMove.java b/decorated-window/src/main/java/com/jetbrains/WindowMove.java deleted file mode 100644 index 5f241303c0..0000000000 --- a/decorated-window/src/main/java/com/jetbrains/WindowMove.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.jetbrains; - -import java.awt.*; - -public interface WindowMove { - /** - * Starts moving the top-level parent window of the given window together with the mouse pointer. - * The intended use is to facilitate the implementation of window management similar to the way - * it is done natively on the platform. - * - * Preconditions for calling this method: - *
    - *
  • WM supports _NET_WM_MOVE_RESIZE (this is checked automatically when an implementation - * of this interface is obtained).
  • - *
  • Mouse pointer is within this window's bounds.
  • - *
  • The mouse button specified by {@code mouseButton} is pressed.
  • - *
- * - * Calling this method will make the window start moving together with the mouse pointer until - * the specified mouse button is released or Esc is pressed. The conditions for cancelling - * the move may differ between WMs. - * - * @param mouseButton indicates the mouse button that was pressed to start moving the window; - * must be one of {@code MouseEvent.BUTTON1}, {@code MouseEvent.BUTTON2}, - * or {@code MouseEvent.BUTTON3}. - */ - void startMovingTogetherWithMouse(Window window, int mouseButton); -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt deleted file mode 100644 index 6abc043863..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt +++ /dev/null @@ -1,289 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.Stable -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposeWindow -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.input.key.KeyEvent -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasurePolicy -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.MeasureScope -import androidx.compose.ui.layout.Placeable -import androidx.compose.ui.layout.layoutId -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.offset -import androidx.compose.ui.window.FrameWindowScope -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowPlacement -import androidx.compose.ui.window.WindowState -import androidx.compose.ui.window.rememberWindowState -import com.jetbrains.JBR -import org.jetbrains.jewel.foundation.Stroke -import org.jetbrains.jewel.foundation.modifier.border -import org.jetbrains.jewel.foundation.modifier.trackWindowActivation -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.window.styling.DecoratedWindowStyle -import org.jetbrains.jewel.window.utils.DesktopPlatform -import java.awt.event.ComponentEvent -import java.awt.event.ComponentListener -import java.awt.event.WindowAdapter -import java.awt.event.WindowEvent - -@Composable -public fun DecoratedWindow( - onCloseRequest: () -> Unit, - state: WindowState = rememberWindowState(), - visible: Boolean = true, - title: String = "", - icon: Painter? = null, - resizable: Boolean = true, - enabled: Boolean = true, - focusable: Boolean = true, - alwaysOnTop: Boolean = false, - onPreviewKeyEvent: (KeyEvent) -> Boolean = { false }, - onKeyEvent: (KeyEvent) -> Boolean = { false }, - style: DecoratedWindowStyle = JewelTheme.defaultDecoratedWindowStyle, - content: @Composable DecoratedWindowScope.() -> Unit, -) { - remember { - if (!JBR.isAvailable()) { - error( - "DecoratedWindow can only be used on JetBrainsRuntime(JBR) platform, " + - "please check the document https://github.com/JetBrains/jewel#int-ui-standalone-theme", - ) - } - } - - // Using undecorated window for linux - val undecorated = DesktopPlatform.Linux == DesktopPlatform.Current - - Window( - onCloseRequest, - state, - visible, - title, - icon, - undecorated, - transparent = false, - resizable, - enabled, - focusable, - alwaysOnTop, - onPreviewKeyEvent, - onKeyEvent, - ) { - var decoratedWindowState by remember { mutableStateOf(DecoratedWindowState.of(window)) } - - DisposableEffect(window) { - val adapter = object : WindowAdapter(), ComponentListener { - override fun windowActivated(e: WindowEvent?) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun windowDeactivated(e: WindowEvent?) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun windowIconified(e: WindowEvent?) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun windowDeiconified(e: WindowEvent?) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun windowStateChanged(e: WindowEvent) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun componentResized(e: ComponentEvent?) { - decoratedWindowState = DecoratedWindowState.of(window) - } - - override fun componentMoved(e: ComponentEvent?) { - // Empty - } - - override fun componentShown(e: ComponentEvent?) { - // Empty - } - - override fun componentHidden(e: ComponentEvent?) { - // Empty - } - } - - window.addWindowListener(adapter) - window.addWindowStateListener(adapter) - window.addComponentListener(adapter) - - onDispose { - window.removeWindowListener(adapter) - window.removeWindowStateListener(adapter) - window.removeComponentListener(adapter) - } - } - - val undecoratedWindowBorder = - if (undecorated && !decoratedWindowState.isMaximized) { - Modifier.border( - Stroke.Alignment.Inside, - style.metrics.borderWidth, - style.colors.borderFor(decoratedWindowState).value, - RectangleShape, - ).padding(style.metrics.borderWidth) - } else { - Modifier - } - - CompositionLocalProvider( - LocalTitleBarInfo provides TitleBarInfo(title, icon), - ) { - Layout( - content = { - val scope = object : DecoratedWindowScope { - override val state: DecoratedWindowState - get() = decoratedWindowState - - override val window: ComposeWindow - get() = this@Window.window - } - scope.content() - }, - modifier = undecoratedWindowBorder.trackWindowActivation(window), - measurePolicy = DecoratedWindowMeasurePolicy, - ) - } - } -} - -@Stable -public interface DecoratedWindowScope : FrameWindowScope { - - override val window: ComposeWindow - - public val state: DecoratedWindowState -} - -private object DecoratedWindowMeasurePolicy : MeasurePolicy { - - override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { - if (measurables.isEmpty()) { - return layout(width = constraints.minWidth, height = constraints.minHeight) {} - } - - val titleBars = measurables.filter { it.layoutId == TITLE_BAR_LAYOUT_ID } - if (titleBars.size > 1) { - error("Window just can have only one title bar") - } - val titleBar = titleBars.firstOrNull() - val titleBarBorder = measurables.firstOrNull { it.layoutId == TITLE_BAR_BORDER_LAYOUT_ID } - - val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0) - - val titleBarPlaceable = titleBar?.measure(contentConstraints) - val titleBarHeight = titleBarPlaceable?.height ?: 0 - - val titleBarBorderPlaceable = titleBarBorder?.measure(contentConstraints) - val titleBarBorderHeight = titleBarBorderPlaceable?.height ?: 0 - - val measuredPlaceable = mutableListOf() - - for (it in measurables) { - if (it.layoutId.toString().startsWith(TITLE_BAR_COMPONENT_LAYOUT_ID_PREFIX)) continue - val offsetConstraints = contentConstraints.offset(vertical = -titleBarHeight - titleBarBorderHeight) - val placeable = it.measure(offsetConstraints) - measuredPlaceable += placeable - } - - return layout(constraints.maxWidth, constraints.maxHeight) { - titleBarPlaceable?.placeRelative(0, 0) - titleBarBorderPlaceable?.placeRelative(0, titleBarHeight) - - measuredPlaceable.forEach { it.placeRelative(0, titleBarHeight + titleBarBorderHeight) } - } - } -} - -@Immutable -@JvmInline -public value class DecoratedWindowState(public val state: ULong) { - - public val isActive: Boolean - get() = state and Active != 0UL - - public val isFullscreen: Boolean - get() = state and Fullscreen != 0UL - - public val isMinimized: Boolean - get() = state and Minimize != 0UL - - public val isMaximized: Boolean - get() = state and Maximize != 0UL - - public fun copy( - fullscreen: Boolean = isFullscreen, - minimized: Boolean = isMinimized, - maximized: Boolean = isMaximized, - active: Boolean = isActive, - ): DecoratedWindowState = - of( - fullscreen = fullscreen, - minimized = minimized, - maximized = maximized, - active = active, - ) - - override fun toString(): String = - "${javaClass.simpleName}(isFullscreen=$isFullscreen, isActive=$isActive)" - - public companion object { - - public val Active: ULong = 1UL shl 0 - public val Fullscreen: ULong = 1UL shl 1 - public val Minimize: ULong = 1UL shl 2 - public val Maximize: ULong = 1UL shl 3 - - public fun of( - fullscreen: Boolean = false, - minimized: Boolean = false, - maximized: Boolean = false, - active: Boolean = true, - ): DecoratedWindowState = - DecoratedWindowState( - (if (fullscreen) Fullscreen else 0UL) or - (if (minimized) Minimize else 0UL) or - (if (maximized) Maximize else 0UL) or - (if (active) Active else 0UL), - ) - - public fun of(window: ComposeWindow): DecoratedWindowState = - of( - fullscreen = window.placement == WindowPlacement.Fullscreen, - minimized = window.isMinimized, - maximized = window.placement == WindowPlacement.Maximized, - active = window.isActive, - ) - } -} - -internal data class TitleBarInfo(val title: String, val icon: Painter?) - -internal val LocalTitleBarInfo: ProvidableCompositionLocal = - compositionLocalOf { - error("LocalTitleBarInfo not provided, TitleBar must be used in DecoratedWindow") - } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt deleted file mode 100644 index 93d672e515..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.window.styling.DecoratedWindowStyle -import org.jetbrains.jewel.window.styling.LocalDecoratedWindowStyle -import org.jetbrains.jewel.window.styling.LocalTitleBarStyle -import org.jetbrains.jewel.window.styling.TitleBarStyle - -public val JewelTheme.Companion.defaultTitleBarStyle: TitleBarStyle - @Composable @ReadOnlyComposable - get() = LocalTitleBarStyle.current - -public val JewelTheme.Companion.defaultDecoratedWindowStyle: DecoratedWindowStyle - @Composable @ReadOnlyComposable - get() = LocalDecoratedWindowStyle.current diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt deleted file mode 100644 index 04e50248a1..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt +++ /dev/null @@ -1,134 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerButton -import androidx.compose.ui.input.pointer.PointerEventPass -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.onPointerEvent -import androidx.compose.ui.platform.LocalViewConfiguration -import androidx.compose.ui.unit.dp -import com.jetbrains.JBR -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.component.Icon -import org.jetbrains.jewel.ui.component.IconButton -import org.jetbrains.jewel.ui.component.styling.IconButtonStyle -import org.jetbrains.jewel.ui.painter.PainterHint -import org.jetbrains.jewel.ui.painter.PainterProvider -import org.jetbrains.jewel.ui.painter.PainterProviderScope -import org.jetbrains.jewel.ui.painter.PainterSuffixHint -import org.jetbrains.jewel.window.styling.TitleBarStyle -import java.awt.Frame -import java.awt.event.MouseEvent -import java.awt.event.WindowEvent - -@Composable -internal fun DecoratedWindowScope.TitleBarOnLinux( - modifier: Modifier = Modifier, - gradientStartColor: Color = Color.Unspecified, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, -) { - var lastPress = 0L - val viewConfig = LocalViewConfiguration.current - TitleBarImpl( - modifier.onPointerEvent(PointerEventType.Press, PointerEventPass.Main) { - if (this.currentEvent.button == PointerButton.Primary && - this.currentEvent.changes.any { changed -> !changed.isConsumed } - ) { - JBR.getWindowMove()?.startMovingTogetherWithMouse(window, MouseEvent.BUTTON1) - if (System.currentTimeMillis() - lastPress in - viewConfig.doubleTapMinTimeMillis..viewConfig.doubleTapTimeoutMillis - ) { - if (state.isMaximized) { - window.extendedState = Frame.NORMAL - } else { - window.extendedState = Frame.MAXIMIZED_BOTH - } - } - lastPress = System.currentTimeMillis() - } - }, - gradientStartColor, - style, - { _, _ -> PaddingValues(0.dp) }, - ) { state -> - CloseButton( - { window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING)) }, - state, - style, - ) - - if (state.isMaximized) { - ControlButton( - { window.extendedState = Frame.NORMAL }, - state, - style.icons.restoreButton, - "Restore", - ) - } else { - ControlButton( - { window.extendedState = Frame.MAXIMIZED_BOTH }, - state, - style.icons.maximizeButton, - "Maximize", - ) - } - ControlButton( - { window.extendedState = Frame.ICONIFIED }, - state, - style.icons.minimizeButton, - "Minimize", - ) - content(state) - } -} - -@Composable -private fun TitleBarScope.CloseButton( - onClick: () -> Unit, - state: DecoratedWindowState, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, -) { - ControlButton( - onClick, - state, - style.icons.closeButton, - "Close", - style, - style.paneCloseButtonStyle, - ) -} - -@Composable -private fun TitleBarScope.ControlButton( - onClick: () -> Unit, - state: DecoratedWindowState, - painterProvider: PainterProvider, - description: String, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - iconButtonStyle: IconButtonStyle = style.paneButtonStyle, -) { - IconButton( - onClick, - Modifier.align(Alignment.End).focusable(false).size(style.metrics.titlePaneButtonSize), - style = iconButtonStyle, - ) { - Icon( - painterProvider.getPainter(if (state.isActive) PainterHint else Inactive).value, - description, - ) - } -} - -private object Inactive : PainterSuffixHint() { - - override fun PainterProviderScope.suffix(): String = "Inactive" - - override fun toString(): String = "Inactive" -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt deleted file mode 100644 index 63475e2d14..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt +++ /dev/null @@ -1,103 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.dp -import com.jetbrains.JBR -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.window.styling.TitleBarStyle -import org.jetbrains.jewel.window.utils.macos.MacUtil - -public fun Modifier.newFullscreenControls(newControls: Boolean = true): Modifier = - this then NewFullscreenControlsElement( - newControls, - debugInspectorInfo { - name = "newFullscreenControls" - value = newControls - }, - ) - -private class NewFullscreenControlsElement( - val newControls: Boolean, - val inspectorInfo: InspectorInfo.() -> Unit, -) : ModifierNodeElement() { - - override fun create(): NewFullscreenControlsNode = - NewFullscreenControlsNode(newControls) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - val otherModifier = other as? NewFullscreenControlsElement - ?: return false - return newControls == otherModifier.newControls - } - - override fun hashCode(): Int = newControls.hashCode() - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } - - override fun update(node: NewFullscreenControlsNode) { - node.newControls = newControls - } -} - -private class NewFullscreenControlsNode(var newControls: Boolean) : Modifier.Node() - -@Composable -internal fun DecoratedWindowScope.TitleBarOnMacOs( - modifier: Modifier = Modifier, - gradientStartColor: Color = Color.Unspecified, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, -) { - val newFullscreenControls = modifier.foldOut(false) { e, r -> - if (e is NewFullscreenControlsElement) { - e.newControls - } else { - r - } - } - - if (newFullscreenControls) { - System.setProperty("apple.awt.newFullScreeControls", true.toString()) - System.setProperty( - "apple.awt.newFullScreeControls.background", - "${style.colors.fullscreenControlButtonsBackground.toArgb()}", - ) - MacUtil.updateColors(window) - } else { - System.clearProperty("apple.awt.newFullScreeControls") - System.clearProperty("apple.awt.newFullScreeControls.background") - } - - val titleBar = remember { JBR.getWindowDecorations().createCustomTitleBar() } - - TitleBarImpl( - modifier = modifier.customTitleBarMouseEventHandler(titleBar), - gradientStartColor = gradientStartColor, - style = style, - applyTitleBar = { height, state -> - if (state.isFullscreen) { - MacUtil.updateFullScreenButtons(window) - } - titleBar.height = height.value - JBR.getWindowDecorations().setCustomTitleBar(window, titleBar) - - if (state.isFullscreen && newFullscreenControls) { - PaddingValues(start = 80.dp) - } else { - PaddingValues(start = titleBar.leftInset.dp, end = titleBar.rightInset.dp) - } - }, - content = content, - ) -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt deleted file mode 100644 index e342f36bae..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerEventPass -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.dp -import com.jetbrains.JBR -import com.jetbrains.WindowDecorations.CustomTitleBar -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.isActive -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.util.isDark -import org.jetbrains.jewel.window.styling.TitleBarStyle - -@Composable -internal fun DecoratedWindowScope.TitleBarOnWindows( - modifier: Modifier = Modifier, - gradientStartColor: Color = Color.Unspecified, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, -) { - val titleBar = remember { JBR.getWindowDecorations().createCustomTitleBar() } - - TitleBarImpl( - modifier = modifier.customTitleBarMouseEventHandler(titleBar), - gradientStartColor = gradientStartColor, - style = style, - applyTitleBar = { height, _ -> - titleBar.height = height.value - titleBar.putProperty("controls.dark", style.colors.background.isDark()) - JBR.getWindowDecorations().setCustomTitleBar(window, titleBar) - PaddingValues(start = titleBar.leftInset.dp, end = titleBar.rightInset.dp) - }, - content = content, - ) -} - -internal fun Modifier.customTitleBarMouseEventHandler(titleBar: CustomTitleBar): Modifier = - pointerInput(Unit) { - val currentContext = currentCoroutineContext() - awaitPointerEventScope { - var inUserControl = false - while (currentContext.isActive) { - val event = awaitPointerEvent(PointerEventPass.Main) - event.changes.forEach { - if (!it.isConsumed && !inUserControl) { - titleBar.forceHitTest(false) - } else { - if (event.type == PointerEventType.Press) { - inUserControl = true - } - if (event.type == PointerEventType.Release) { - inUserControl = false - } - titleBar.forceHitTest(true) - } - } - } - } - } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt deleted file mode 100644 index acd1218ff1..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt +++ /dev/null @@ -1,294 +0,0 @@ -package org.jetbrains.jewel.window - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusProperties -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.isUnspecified -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasurePolicy -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.MeasureScope -import androidx.compose.ui.layout.Placeable -import androidx.compose.ui.layout.layoutId -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.node.ParentDataModifierNode -import androidx.compose.ui.platform.InspectableValue -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.NoInspectorInfo -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.offset -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.foundation.theme.LocalContentColor -import org.jetbrains.jewel.foundation.theme.OverrideDarkMode -import org.jetbrains.jewel.ui.component.styling.LocalDefaultDropdownStyle -import org.jetbrains.jewel.ui.component.styling.LocalIconButtonStyle -import org.jetbrains.jewel.ui.util.isDark -import org.jetbrains.jewel.window.styling.TitleBarStyle -import org.jetbrains.jewel.window.utils.DesktopPlatform -import org.jetbrains.jewel.window.utils.macos.MacUtil -import java.awt.Window -import kotlin.math.max - -internal const val TITLE_BAR_COMPONENT_LAYOUT_ID_PREFIX = "__TITLE_BAR_" - -internal const val TITLE_BAR_LAYOUT_ID = "__TITLE_BAR_CONTENT__" - -internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" - -@Composable -public fun DecoratedWindowScope.TitleBar( - modifier: Modifier = Modifier, - gradientStartColor: Color = Color.Unspecified, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, -) { - when (DesktopPlatform.Current) { - DesktopPlatform.Linux -> TitleBarOnLinux(modifier, gradientStartColor, style, content) - DesktopPlatform.Windows -> TitleBarOnWindows(modifier, gradientStartColor, style, content) - DesktopPlatform.MacOS -> TitleBarOnMacOs(modifier, gradientStartColor, style, content) - DesktopPlatform.Unknown -> error("TitleBar is not supported on this platform(${System.getProperty("os.name")})") - } -} - -@Composable -internal fun DecoratedWindowScope.TitleBarImpl( - modifier: Modifier = Modifier, - gradientStartColor: Color = Color.Unspecified, - style: TitleBarStyle = JewelTheme.defaultTitleBarStyle, - applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, - content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, -) { - val titleBarInfo = LocalTitleBarInfo.current - - val background by style.colors.backgroundFor(state) - - val density = LocalDensity.current - - val backgroundBrush = remember(background, gradientStartColor) { - if (gradientStartColor.isUnspecified) { - SolidColor(background) - } else { - with(density) { - Brush.horizontalGradient( - 0.0f to background, - 0.5f to gradientStartColor, - 1.0f to background, - startX = style.metrics.gradientStartX.toPx(), - endX = style.metrics.gradientEndX.toPx(), - ) - } - } - } - - Layout( - content = { - CompositionLocalProvider( - LocalContentColor provides style.colors.content, - LocalIconButtonStyle provides style.iconButtonStyle, - LocalDefaultDropdownStyle provides style.dropdownStyle, - ) { - OverrideDarkMode(background.isDark()) { - val scope = TitleBarScopeImpl(titleBarInfo.title, titleBarInfo.icon) - scope.content(state) - } - } - }, - modifier = modifier.background(backgroundBrush) - .focusProperties { canFocus = false } - .layoutId(TITLE_BAR_LAYOUT_ID) - .height(style.metrics.height) - .onSizeChanged { with(density) { applyTitleBar(it.height.toDp(), state) } } - .fillMaxWidth(), - measurePolicy = rememberTitleBarMeasurePolicy( - window, - state, - applyTitleBar, - ), - ) - - Spacer( - Modifier.layoutId(TITLE_BAR_BORDER_LAYOUT_ID) - .height(1.dp) - .fillMaxWidth() - .background(style.colors.border), - ) -} - -internal class TitleBarMeasurePolicy( - private val window: Window, - private val state: DecoratedWindowState, - private val applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, -) : MeasurePolicy { - - override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { - if (measurables.isEmpty()) { - return layout(width = constraints.minWidth, height = constraints.minHeight) {} - } - - var occupiedSpaceHorizontally = 0 - - var maxSpaceVertically = constraints.minHeight - val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0) - val measuredPlaceable = mutableListOf>() - - for (it in measurables) { - val placeable = it.measure(contentConstraints.offset(horizontal = -occupiedSpaceHorizontally)) - if (constraints.maxWidth < occupiedSpaceHorizontally + placeable.width) { - break - } - occupiedSpaceHorizontally += placeable.width - maxSpaceVertically = max(maxSpaceVertically, placeable.height) - measuredPlaceable += it to placeable - } - - val boxHeight = maxSpaceVertically - - val contentPadding = applyTitleBar(boxHeight.toDp(), state) - - val leftInset = contentPadding.calculateLeftPadding(layoutDirection).roundToPx() - val rightInset = contentPadding.calculateRightPadding(layoutDirection).roundToPx() - - occupiedSpaceHorizontally += leftInset - occupiedSpaceHorizontally += rightInset - - val boxWidth = maxOf(constraints.minWidth, occupiedSpaceHorizontally) - - return layout(boxWidth, boxHeight) { - if (state.isFullscreen) { - MacUtil.updateFullScreenButtons(window) - } - val placeableGroups = - measuredPlaceable.groupBy { (measurable, _) -> - (measurable.parentData as? TitleBarChildDataNode)?.horizontalAlignment - ?: Alignment.CenterHorizontally - } - - var headUsedSpace = leftInset - var trailerUsedSpace = rightInset - - placeableGroups[Alignment.Start]?.forEach { (_, placeable) -> - val x = headUsedSpace - val y = Alignment.CenterVertically.align(placeable.height, boxHeight) - placeable.placeRelative(x, y) - headUsedSpace += placeable.width - } - placeableGroups[Alignment.End]?.forEach { (_, placeable) -> - val x = boxWidth - placeable.width - trailerUsedSpace - val y = Alignment.CenterVertically.align(placeable.height, boxHeight) - placeable.placeRelative(x, y) - trailerUsedSpace += placeable.width - } - - val centerPlaceable = placeableGroups[Alignment.CenterHorizontally].orEmpty() - - val requiredCenterSpace = centerPlaceable.sumOf { it.second.width } - val minX = headUsedSpace - val maxX = boxWidth - trailerUsedSpace - requiredCenterSpace - var centerX = (boxWidth - requiredCenterSpace) / 2 - - if (minX <= maxX) { - if (centerX > maxX) { - centerX = maxX - } - if (centerX < minX) { - centerX = minX - } - - centerPlaceable.forEach { (_, placeable) -> - val x = centerX - val y = Alignment.CenterVertically.align(placeable.height, boxHeight) - placeable.placeRelative(x, y) - centerX += placeable.width - } - } - } - } -} - -@Composable -internal fun rememberTitleBarMeasurePolicy( - window: Window, - state: DecoratedWindowState, - applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, -): MeasurePolicy = - remember(window, state, applyTitleBar) { - TitleBarMeasurePolicy(window, state, applyTitleBar) - } - -public interface TitleBarScope { - - public val title: String - - public val icon: Painter? - - @Stable - public fun Modifier.align(alignment: Alignment.Horizontal): Modifier -} - -private class TitleBarScopeImpl( - override val title: String, - override val icon: Painter?, -) : TitleBarScope { - - override fun Modifier.align(alignment: Alignment.Horizontal): Modifier = - this then TitleBarChildDataElement( - alignment, - debugInspectorInfo { - name = "align" - value = alignment - }, - ) -} - -private class TitleBarChildDataElement( - val horizontalAlignment: Alignment.Horizontal, - val inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo, -) : ModifierNodeElement(), InspectableValue { - - override fun create(): TitleBarChildDataNode = TitleBarChildDataNode(horizontalAlignment) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - val otherModifier = other as? TitleBarChildDataElement ?: return false - return horizontalAlignment == otherModifier.horizontalAlignment - } - - override fun hashCode(): Int = horizontalAlignment.hashCode() - - override fun update(node: TitleBarChildDataNode) { - node.horizontalAlignment = horizontalAlignment - } - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } -} - -private class TitleBarChildDataNode( - var horizontalAlignment: Alignment.Horizontal, -) : ParentDataModifierNode, Modifier.Node() { - - override fun Density.modifyParentData(parentData: Any?) = - this@TitleBarChildDataNode -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt deleted file mode 100644 index cc15f5ac62..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.jetbrains.jewel.window.styling - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.State -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp -import org.jetbrains.jewel.foundation.GenerateDataFunctions -import org.jetbrains.jewel.window.DecoratedWindowState - -@Immutable -@GenerateDataFunctions -public class DecoratedWindowStyle( - public val colors: DecoratedWindowColors, - public val metrics: DecoratedWindowMetrics, -) { - - public companion object -} - -@Immutable -@GenerateDataFunctions -public class DecoratedWindowColors( - public val border: Color, - public val borderInactive: Color, -) { - - @Composable - public fun borderFor(state: DecoratedWindowState): State = - rememberUpdatedState( - when { - !state.isActive -> borderInactive - else -> border - }, - ) - - public companion object -} - -@Immutable -@GenerateDataFunctions -public class DecoratedWindowMetrics(public val borderWidth: Dp) { - - public companion object -} - -public val LocalDecoratedWindowStyle: ProvidableCompositionLocal = - staticCompositionLocalOf { - error("No DecoratedWindowStyle provided. Have you forgotten the theme?") - } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt deleted file mode 100644 index ca0d907f2c..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt +++ /dev/null @@ -1,102 +0,0 @@ -package org.jetbrains.jewel.window.styling - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize -import org.jetbrains.jewel.foundation.GenerateDataFunctions -import org.jetbrains.jewel.ui.component.styling.DropdownStyle -import org.jetbrains.jewel.ui.component.styling.IconButtonStyle -import org.jetbrains.jewel.ui.painter.PainterProvider -import org.jetbrains.jewel.window.DecoratedWindowState - -@Stable -@GenerateDataFunctions -public class TitleBarStyle( - public val colors: TitleBarColors, - public val metrics: TitleBarMetrics, - public val icons: TitleBarIcons, - public val dropdownStyle: DropdownStyle, - public val iconButtonStyle: IconButtonStyle, - public val paneButtonStyle: IconButtonStyle, - public val paneCloseButtonStyle: IconButtonStyle, -) { - - public companion object -} - -@Immutable -@GenerateDataFunctions -public class TitleBarColors( - public val background: Color, - public val inactiveBackground: Color, - public val content: Color, - public val border: Color, - - // The background color for newControlButtons(three circles in left top corner) in MacOS - // fullscreen mode - public val fullscreenControlButtonsBackground: Color, - - // The hover and press background color for window control buttons(minimize, maximize) in Linux - public val titlePaneButtonHoveredBackground: Color, - public val titlePaneButtonPressedBackground: Color, - - // The hover and press background color for window close button in Linux - public val titlePaneCloseButtonHoveredBackground: Color, - public val titlePaneCloseButtonPressedBackground: Color, - - // The hover and press background color for IconButtons in title bar content - public val iconButtonHoveredBackground: Color, - public val iconButtonPressedBackground: Color, - - // The hover and press background color for Dropdown in title bar content - public val dropdownPressedBackground: Color, - public val dropdownHoveredBackground: Color, -) { - - @Composable - public fun backgroundFor(state: DecoratedWindowState): State = - rememberUpdatedState( - when { - !state.isActive -> inactiveBackground - else -> background - }, - ) - - public companion object -} - -@Immutable -@GenerateDataFunctions -public class TitleBarMetrics( - public val height: Dp, - public val gradientStartX: Dp, - public val gradientEndX: Dp, - public val titlePaneButtonSize: DpSize, -) { - - public companion object -} - -@Immutable -@GenerateDataFunctions -public class TitleBarIcons( - public val minimizeButton: PainterProvider, - public val maximizeButton: PainterProvider, - public val restoreButton: PainterProvider, - public val closeButton: PainterProvider, -) { - - public companion object -} - -public val LocalTitleBarStyle: ProvidableCompositionLocal = - staticCompositionLocalOf { - error("No TitleBarStyle provided. Have you forgotten the theme?") - } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/DesktopPlatform.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/DesktopPlatform.kt deleted file mode 100644 index 51872dc474..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/DesktopPlatform.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.jetbrains.jewel.window.utils - -public enum class DesktopPlatform { - Linux, - Windows, - MacOS, - Unknown, - ; - - public companion object { - - public val Current: DesktopPlatform by lazy { - val name = System.getProperty("os.name") - when { - name?.startsWith("Linux") == true -> Linux - name?.startsWith("Win") == true -> Windows - name == "Mac OS X" -> MacOS - else -> Unknown - } - } - } -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/JnaLoader.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/JnaLoader.kt deleted file mode 100644 index 7e136b0f65..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/JnaLoader.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.jetbrains.jewel.window.utils - -import com.sun.jna.Native -import java.util.logging.Level -import java.util.logging.Logger -import kotlin.system.measureTimeMillis - -internal object JnaLoader { - - private var loaded: Boolean? = null - private val logger = Logger.getLogger(JnaLoader::class.java.simpleName) - - @Synchronized - fun load() { - if (loaded == null) { - loaded = false - try { - val time = measureTimeMillis { Native.POINTER_SIZE } - logger.info("JNA library (${Native.POINTER_SIZE shl 3}-bit) loaded in $time ms") - loaded = true - } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { - logger.log( - Level.WARNING, - "Unable to load JNA library(os=${ - System.getProperty("os.name") - } ${System.getProperty("os.version")}, jna.boot.library.path=${ - System.getProperty("jna.boot.library.path") - })", - t, - ) - } - } - } - - @get:Synchronized - val isLoaded: Boolean - get() { - if (loaded == null) { - load() - } - return loaded ?: false - } -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/UnsafeAccessing.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/UnsafeAccessing.kt deleted file mode 100644 index ed0e7920a2..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/UnsafeAccessing.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.jetbrains.jewel.window.utils - -import sun.misc.Unsafe -import java.lang.reflect.AccessibleObject -import java.util.logging.Level -import java.util.logging.Logger - -internal object UnsafeAccessing { - - private val logger = Logger.getLogger(UnsafeAccessing::class.java.simpleName) - - private val unsafe: Any? by lazy { - try { - val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe") - theUnsafe.isAccessible = true - theUnsafe.get(null) as Unsafe - } catch (@Suppress("TooGenericExceptionCaught") error: Throwable) { - logger.log(Level.WARNING, "Unsafe accessing initializing failed.", error) - null - } - } - - val desktopModule by lazy { ModuleLayer.boot().findModule("java.desktop").get() } - - val ownerModule: Module by lazy { this.javaClass.module } - - private val isAccessibleFieldOffset: Long? by lazy { - try { - (unsafe as? Unsafe)?.objectFieldOffset(Parent::class.java.getDeclaredField("first")) - } catch (_: Throwable) { - null - } - } - - private val implAddOpens by lazy { - try { - Module::class.java - .getDeclaredMethod("implAddOpens", String::class.java, Module::class.java) - .accessible() - } catch (_: Throwable) { - null - } - } - - fun assignAccessibility(obj: AccessibleObject) { - try { - val theUnsafe = unsafe as? Unsafe ?: return - val offset = isAccessibleFieldOffset ?: return - theUnsafe.putBooleanVolatile(obj, offset, true) - } catch (_: Throwable) { - // ignore - } - } - - fun assignAccessibility(module: Module, packages: List) { - try { - packages.forEach { implAddOpens?.invoke(module, it, ownerModule) } - } catch (_: Throwable) { - // ignore - } - } - - private class Parent { - - var first = false - - @Volatile - var second: Any? = null - } -} - -internal fun T.accessible(): T = - apply { UnsafeAccessing.assignAccessibility(this) } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/Foundation.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/Foundation.kt deleted file mode 100644 index 193b29c7eb..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/Foundation.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.jetbrains.jewel.window.utils.macos - -import com.sun.jna.Function -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import org.jetbrains.jewel.window.utils.JnaLoader -import java.lang.reflect.Proxy -import java.util.Arrays -import java.util.Collections -import java.util.logging.Level -import java.util.logging.Logger - -internal object Foundation { - - private val logger = Logger.getLogger(Foundation::class.java.simpleName) - - init { - if (!JnaLoader.isLoaded) { - logger.log(Level.WARNING, "JNA is not loaded") - } - } - - private val myFoundationLibrary: FoundationLibrary? by lazy { - try { - Native.load( - "Foundation", - FoundationLibrary::class.java, - Collections.singletonMap("jna.encoding", "UTF8"), - ) - } catch (_: Throwable) { - null - } - } - - private val myObjcMsgSend: Function? by lazy { - try { - (Proxy.getInvocationHandler(myFoundationLibrary) as Library.Handler).nativeLibrary.getFunction("objc_msgSend") - } catch (_: Throwable) { - null - } - } - - /** - * Get the ID of the NSClass with className - */ - fun getObjcClass(className: String?): ID? = myFoundationLibrary?.objc_getClass(className) - - fun getProtocol(name: String?): ID? = myFoundationLibrary?.objc_getProtocol(name) - - fun createSelector(s: String?): Pointer? = myFoundationLibrary?.sel_registerName(s) - - private fun prepInvoke(id: ID?, selector: Pointer?, args: Array): Array { - val invokArgs = arrayOfNulls(args.size + 2) - invokArgs[0] = id - invokArgs[1] = selector - System.arraycopy(args, 0, invokArgs, 2, args.size) - return invokArgs - } - - // objc_msgSend is called with the calling convention of the target method - // on x86_64 this does not make a difference, but arm64 uses a different calling convention for varargs - // it is therefore important to not call objc_msgSend as a vararg function - operator fun invoke(id: ID?, selector: Pointer?, vararg args: Any?): ID = - ID(myObjcMsgSend?.invokeLong(prepInvoke(id, selector, args)) ?: 0) - - /** - * Invokes the given vararg selector. - * Expects `NSArray arrayWithObjects:(id), ...` like signature, i.e. exactly one fixed argument, followed by varargs. - */ - fun invokeVarArg(id: ID?, selector: Pointer?, vararg args: Any?): ID { - // c functions and objc methods have at least 1 fixed argument, we therefore need to separate out the first argument - return myFoundationLibrary?.objc_msgSend( - id, - selector, - args[0], - *Arrays.copyOfRange(args, 1, args.size), - ) ?: ID.NIL - } - - operator fun invoke(cls: String?, selector: String?, vararg args: Any?): ID = - invoke(getObjcClass(cls), createSelector(selector), *args) - - fun invokeVarArg(cls: String?, selector: String?, vararg args: Any?): ID = - invokeVarArg(getObjcClass(cls), createSelector(selector), *args) - - fun safeInvoke(stringCls: String?, stringSelector: String?, vararg args: Any?): ID { - val cls = getObjcClass(stringCls) - val selector = createSelector(stringSelector) - if (!invoke(cls, "respondsToSelector:", selector).booleanValue()) { - error("Missing selector $stringSelector for $stringCls") - } - return invoke(cls, selector, *args) - } - - operator fun invoke(id: ID?, selector: String?, vararg args: Any?): ID = - invoke(id, createSelector(selector), *args) -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/FoundationLibrary.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/FoundationLibrary.kt deleted file mode 100644 index a4c93807e2..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/FoundationLibrary.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.jetbrains.jewel.window.utils.macos - -import com.sun.jna.Callback -import com.sun.jna.Library -import com.sun.jna.Pointer - -internal interface FoundationLibrary : Library { - fun NSLog(pString: Pointer?, thing: Any?) - fun NSFullUserName(): ID? - fun objc_allocateClassPair(supercls: ID?, name: String?, extraBytes: Int): ID? - fun objc_registerClassPair(cls: ID?) - fun CFStringCreateWithBytes( - allocator: Pointer?, - bytes: ByteArray?, - byteCount: Int, - encoding: Int, - isExternalRepresentation: Byte, - ): ID? - - fun CFStringGetCString(theString: ID?, buffer: ByteArray?, bufferSize: Int, encoding: Int): Byte - fun CFStringGetLength(theString: ID?): Int - fun CFStringConvertNSStringEncodingToEncoding(nsEncoding: Long): Long - fun CFStringConvertEncodingToIANACharSetName(cfEncoding: Long): ID? - fun CFStringConvertIANACharSetNameToEncoding(encodingName: ID?): Long - fun CFStringConvertEncodingToNSStringEncoding(cfEncoding: Long): Long - fun CFRetain(cfTypeRef: ID?) - fun CFRelease(cfTypeRef: ID?) - fun CFGetRetainCount(cfTypeRef: Pointer?): Int - fun objc_getClass(className: String?): ID? - fun objc_getProtocol(name: String?): ID? - fun class_createInstance(pClass: ID?, extraBytes: Int): ID? - fun sel_registerName(selectorName: String?): Pointer? - fun class_replaceMethod(cls: ID?, selName: Pointer?, impl: Callback?, types: String?): ID? - fun objc_getMetaClass(name: String?): ID? - - /** - * Note: Vararg version. Should only be used only for selectors with a single fixed argument followed by varargs. - */ - fun objc_msgSend(receiver: ID?, selector: Pointer?, firstArg: Any?, vararg args: Any?): ID? - fun class_respondsToSelector(cls: ID?, selName: Pointer?): Boolean - fun class_addMethod(cls: ID?, selName: Pointer?, imp: Callback?, types: String?): Boolean - fun class_addMethod(cls: ID?, selName: Pointer?, imp: ID?, types: String?): Boolean - fun class_addProtocol(aClass: ID?, protocol: ID?): Boolean - fun class_isMetaClass(cls: ID?): Boolean - fun NSStringFromSelector(selector: Pointer?): ID? - fun NSStringFromClass(aClass: ID?): ID? - fun objc_getClass(clazz: Pointer?): Pointer? - - companion object { - const val kCFStringEncodingMacRoman = 0 - const val kCFStringEncodingWindowsLatin1 = 0x0500 - const val kCFStringEncodingISOLatin1 = 0x0201 - const val kCFStringEncodingNextStepLatin = 0x0B01 - const val kCFStringEncodingASCII = 0x0600 - const val kCFStringEncodingUnicode = 0x0100 - const val kCFStringEncodingUTF8 = 0x08000100 - const val kCFStringEncodingNonLossyASCII = 0x0BFF - const val kCFStringEncodingUTF16 = 0x0100 - const val kCFStringEncodingUTF16BE = 0x10000100 - const val kCFStringEncodingUTF16LE = 0x14000100 - const val kCFStringEncodingUTF32 = 0x0c000100 - const val kCFStringEncodingUTF32BE = 0x18000100 - const val kCFStringEncodingUTF32LE = 0x1c000100 - - // https://developer.apple.com/library/mac/documentation/Carbon/Reference/CGWindow_Reference/Constants/Constants.html#//apple_ref/doc/constant_group/Window_List_Option_Constants - const val kCGWindowListOptionAll = 0 - const val kCGWindowListOptionOnScreenOnly = 1 - const val kCGWindowListOptionOnScreenAboveWindow = 2 - const val kCGWindowListOptionOnScreenBelowWindow = 4 - const val kCGWindowListOptionIncludingWindow = 8 - const val kCGWindowListExcludeDesktopElements = 16 - - // https://developer.apple.com/library/mac/documentation/Carbon/Reference/CGWindow_Reference/Constants/Constants.html#//apple_ref/doc/constant_group/Window_Image_Types - const val kCGWindowImageDefault = 0 - const val kCGWindowImageBoundsIgnoreFraming = 1 - const val kCGWindowImageShouldBeOpaque = 2 - const val kCGWindowImageOnlyShadows = 4 - const val kCGWindowImageBestResolution = 8 - const val kCGWindowImageNominalResolution = 16 - - // see enum NSBitmapImageFileType - const val NSBitmapImageFileTypeTIFF = 0 - const val NSBitmapImageFileTypeBMP = 1 - const val NSBitmapImageFileTypeGIF = 2 - const val NSBitmapImageFileTypeJPEG = 3 - const val NSBitmapImageFileTypePNG = 4 - const val NSBitmapImageFileTypeJPEG2000 = 5 - } -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/ID.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/ID.kt deleted file mode 100644 index df74e25e7f..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/ID.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.jetbrains.jewel.window.utils.macos - -import com.sun.jna.NativeLong - -/** - * Could be an address in memory (if pointer to a class or method) or a - * value (like 0 or 1) - */ -internal class ID : NativeLong { - - constructor() - constructor(peer: Long) : super(peer) - - fun booleanValue(): Boolean = toInt() != 0 - - override fun toByte(): Byte = toInt().toByte() - - override fun toChar(): Char = toInt().toChar() - - override fun toShort(): Short = toInt().toShort() - - @Suppress("RedundantOverride") // Without this, we get a SOE - override fun toInt(): Int = super.toInt() - - companion object { - - @JvmField - val NIL = ID(0L) - } -} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/MacUtil.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/MacUtil.kt deleted file mode 100644 index 248cb170a1..0000000000 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/utils/macos/MacUtil.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.jetbrains.jewel.window.utils.macos - -import org.jetbrains.jewel.window.utils.UnsafeAccessing -import org.jetbrains.jewel.window.utils.accessible -import java.awt.Component -import java.awt.Window -import java.lang.reflect.InvocationTargetException -import java.util.logging.Level -import java.util.logging.Logger -import javax.swing.SwingUtilities - -internal object MacUtil { - - private val logger = Logger.getLogger(MacUtil::class.java.simpleName) - - init { - try { - UnsafeAccessing.assignAccessibility( - UnsafeAccessing.desktopModule, - listOf("sun.awt", "sun.lwawt", "sun.lwawt.macosx"), - ) - } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { - logger.log(Level.WARNING, "Assign access for jdk.desktop failed.", e) - } - } - - fun getWindowFromJavaWindow(w: Window?): ID { - if (w == null) { - return ID.NIL - } - try { - val cPlatformWindow = getPlatformWindow(w) - if (cPlatformWindow != null) { - val ptr = cPlatformWindow.javaClass.superclass.getDeclaredField("ptr") - ptr.setAccessible(true) - return ID(ptr.getLong(cPlatformWindow)) - } - } catch (e: IllegalAccessException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } catch (e: NoSuchFieldException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } - return ID.NIL - } - - fun getPlatformWindow(w: Window): Any? { - try { - val awtAccessor = Class.forName("sun.awt.AWTAccessor") - val componentAccessor = awtAccessor.getMethod("getComponentAccessor").invoke(null) - val getPeer = componentAccessor.javaClass.getMethod("getPeer", Component::class.java).accessible() - val peer = getPeer.invoke(componentAccessor, w) - if (peer != null) { - val cWindowPeerClass: Class<*> = peer.javaClass - val getPlatformWindowMethod = cWindowPeerClass.getDeclaredMethod("getPlatformWindow") - val cPlatformWindow = getPlatformWindowMethod.invoke(peer) - if (cPlatformWindow != null) { - return cPlatformWindow - } - } - } catch (e: NoSuchMethodException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } catch (e: IllegalAccessException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } catch (e: InvocationTargetException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } catch (e: ClassNotFoundException) { - logger.log(Level.WARNING, "Fail to get cPlatformWindow from awt window.", e) - } - return null - } - - fun updateColors(w: Window) { - SwingUtilities.invokeLater { - val window = getWindowFromJavaWindow(w) - val delegate = Foundation.invoke(window, "delegate") - if (Foundation.invoke(delegate, "respondsToSelector:", Foundation.createSelector("updateColors")) - .booleanValue() - ) { - Foundation.invoke(delegate, "updateColors") - } - } - } - - fun updateFullScreenButtons(w: Window) { - SwingUtilities.invokeLater { - val selector = Foundation.createSelector("updateFullScreenButtons") - val window = getWindowFromJavaWindow(w) - val delegate = Foundation.invoke(window, "delegate") - - if (Foundation.invoke(delegate, "respondsToSelector:", selector).booleanValue()) { - Foundation.invoke(delegate, "updateFullScreenButtons") - } - } - } -} diff --git a/gradle.properties b/gradle.properties index 2ce2eecdc4..1208c254b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ kotlin.stdlib.default.dependency=false # See https://jb.gg/intellij-platform-kotlin-oom kotlin.incremental.useClasspathSnapshot=false -bridge.ijp.target=241 +bridge.ijp.target=232 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa0097a6a7..70c8909d16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ composeDesktop = "1.6.0-dev1397" detekt = "1.23.4" dokka = "1.8.20" -idea = "241.9959.31-EAP-SNAPSHOT" +idea = "232.10227.8" ideaGradlePlugin = "1.17.0" jna = "5.14.0" kotlin = "1.8.21" diff --git a/ide-laf-bridge/api/ide-laf-bridge.api b/ide-laf-bridge/api/ide-laf-bridge.api index b0d51fad0a..e029402075 100644 --- a/ide-laf-bridge/api/ide-laf-bridge.api +++ b/ide-laf-bridge/api/ide-laf-bridge.api @@ -78,6 +78,10 @@ public final class org/jetbrains/jewel/bridge/TypographyKt { public static final fun small (Lorg/jetbrains/jewel/ui/component/Typography;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/text/TextStyle; } +public final class org/jetbrains/jewel/bridge/UiThemeExtensionsKt { + public static final fun getIcons (Lcom/intellij/ide/ui/UITheme;)Ljava/util/Map; +} + public final class org/jetbrains/jewel/bridge/actionSystem/ProvideDataKt { public static final fun ComponentDataProviderBridge (Ljavax/swing/JComponent;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V public static final fun provideData (Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier; diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconData.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconData.kt index 69d1b2c759..fb6627088b 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconData.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconData.kt @@ -1,14 +1,16 @@ package org.jetbrains.jewel.bridge import com.intellij.ide.ui.UITheme +import com.intellij.ui.ColorUtil +import org.jetbrains.jewel.foundation.InternalJewelApi import org.jetbrains.jewel.foundation.theme.ThemeIconData -@Suppress("UnstableApiUsage") +@OptIn(InternalJewelApi::class) public fun ThemeIconData.Companion.readFromLaF(): ThemeIconData { val uiTheme = currentUiThemeOrNull() - val bean = uiTheme?.describe() - val iconMap = bean?.icons.orEmpty() - val selectedIconColorPalette = bean?.iconColorsOnSelection.orEmpty() + val iconMap = uiTheme?.icons.orEmpty() + val selectedIconColorPalette = uiTheme?.selectedIconColorPalette.orEmpty() + .mapValues { ColorUtil.fromHex(it.value).rgb } val colorPalette = UITheme.getColorPalette() return ThemeIconData(iconMap, colorPalette, selectedIconColorPalette) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt index e6a9af4231..3651942891 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt @@ -1,7 +1,5 @@ package org.jetbrains.jewel.bridge -import com.intellij.openapi.diagnostic.Logger -import com.intellij.ui.icons.patchIconPath import com.intellij.util.ui.DirProvider import org.jetbrains.jewel.ui.painter.PainterPathHint import org.jetbrains.jewel.ui.painter.PainterProviderScope @@ -11,7 +9,14 @@ internal object BridgeOverride : PainterPathHint { private val dirProvider = DirProvider() - @Suppress("UnstableApiUsage") // patchIconPath() is explicitly open to us + private val patchIconPath by lazy { + val clazz = Class.forName("com.intellij.ui.icons.CachedImageIconKt") + val patchIconPath = + clazz.getMethod("patchIconPath", String::class.java, ClassLoader::class.java) + patchIconPath.isAccessible = true + patchIconPath + } + override fun PainterProviderScope.patch(): String { if (this !is ResourcePainterProviderScope) return path @@ -20,49 +25,17 @@ internal object BridgeOverride : PainterPathHint { // removed (the classloader is set up differently in prod IDEs and when running // from Gradle, and the icon could be in either place depending on the environment) val fallbackPath = path.removePrefix(dirProvider.dir()) - - for (classLoader in classLoaders) { - val patchedPath = patchIconPath(path.removePrefix("/"), classLoader)?.first - ?: patchIconPath(fallbackPath, classLoader)?.first - - // 233 EAP 4 broke path patching horribly; now it can return a - // "reflective path", which is a FQN to an ExpUIIcons entry. - // As a (hopefully) temporary solution, we undo this transformation - // back into the original path. - if (patchedPath?.startsWith("com.intellij.icons.ExpUiIcons") == true) { - return inferActualPathFromReflectivePath(patchedPath) - } - - if (patchedPath != null) { - return patchedPath - } - } - return path - } - - private fun inferActualPathFromReflectivePath(patchedPath: String): String { - val iconPath = patchedPath.removePrefix("com.intellij.icons.ExpUiIcons.") - - return buildString { - append("expui/") - iconPath.split('.') - .map { it.trim() } - .filter { it.isNotEmpty() } - .forEach { - append(it.first().lowercaseChar()) - append(it.drop(1)) - append('/') + val patchedPath = + classLoaders + .firstNotNullOfOrNull { classLoader -> + val patchedPathAndClassLoader = + patchIconPath.invoke(null, path.removePrefix("/"), classLoader) + ?: patchIconPath.invoke(null, fallbackPath, classLoader) + patchedPathAndClassLoader as? Pair<*, *> } - replace(length - 1, length, "") // Drop last '/' - if (iconPath.contains("_dark")) append("_dark") - append(".svg") + ?.first as? String - Logger.getInstance("IconsPathPatching") - .warn( - "IntelliJ returned a reflective path: $patchedPath for $iconPath." + - " We reverted that to a plausible-looking resource path: ${toString()}", - ) - } + return patchedPath ?: path } override fun toString(): String = "BridgeOverride" diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt index 839a666573..6f600491c7 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -10,11 +10,12 @@ import org.jetbrains.jewel.ui.painter.BasePainterHintsProvider import org.jetbrains.jewel.ui.painter.PainterHint import org.jetbrains.jewel.ui.painter.hints.Dark import org.jetbrains.jewel.ui.painter.hints.HiDpi +import org.jetbrains.jewel.ui.util.fromRGBAHexStringOrNull @InternalJewelApi public class BridgePainterHintsProvider private constructor( isDark: Boolean, - intellijIconPalette: Map = emptyMap(), + intellijIconPalette: Map = emptyMap(), themeIconPalette: Map = emptyMap(), themeColorPalette: Map = emptyMap(), ) : BasePainterHintsProvider(isDark, intellijIconPalette, themeIconPalette, themeColorPalette) { @@ -31,21 +32,20 @@ public class BridgePainterHintsProvider private constructor( private val logger = thisLogger() - @Suppress("UnstableApiUsage") // We need to call @Internal APIs public operator fun invoke(isDark: Boolean): BasePainterHintsProvider { val uiTheme = currentUiThemeOrNull() ?: return BridgePainterHintsProvider(isDark) logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") - val bean = uiTheme.describe() - val iconColorPalette = - (bean.colorPalette as Map).mapValues { - when (val value = it.value) { - is String -> value + val iconColorPalette = uiTheme.iconColorPalette + val keyPalette = UITheme.getColorPalette() + val themeColors = uiTheme.colors.orEmpty() + .mapValues { (_, v) -> + when (v) { + is Int -> Color(v) + is String -> Color.fromRGBAHexStringOrNull(v) else -> null } } - val keyPalette = UITheme.getColorPalette() - val themeColors = bean.colors.mapValues { (_, v) -> Color(v) } return BridgePainterHintsProvider(isDark, keyPalette, iconColorPalette, themeColors) } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/UiThemeExtensions.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/UiThemeExtensions.kt index 81a6cd6da6..c4a75d91af 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/UiThemeExtensions.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/UiThemeExtensions.kt @@ -1,8 +1,45 @@ package org.jetbrains.jewel.bridge import com.intellij.ide.ui.LafManager -import com.intellij.ide.ui.laf.UIThemeLookAndFeelInfo +import com.intellij.ide.ui.UITheme +import com.intellij.ide.ui.laf.UIThemeBasedLookAndFeelInfo +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.jewel.foundation.InternalJewelApi +import java.lang.reflect.Field -@Suppress("UnstableApiUsage") -internal fun currentUiThemeOrNull(): UIThemeLookAndFeelInfo? = - LafManager.getInstance().currentUIThemeLookAndFeel?.takeIf { it.isInitialized } +private val logger = Logger.getInstance("UiThemeExtensions") + +private val classUITheme + get() = UITheme::class.java + +@InternalJewelApi +internal fun currentUiThemeOrNull() = + (LafManager.getInstance().currentLookAndFeel as? UIThemeBasedLookAndFeelInfo)?.theme + +@InternalJewelApi +public val UITheme.icons: Map + get() = readMapField(classUITheme.getDeclaredField("icons")) + .filterKeys { it != "ColorPalette" } + +internal val UITheme.iconColorPalette: Map + get() = readMapField>(classUITheme.getDeclaredField("icons"))["ColorPalette"] + .orEmpty() + +internal val UITheme.selectedIconColorPalette: Map + get() = readMapField(classUITheme.getDeclaredField("iconColorsOnSelection")) + +private fun UITheme.readMapField(field: Field): Map { + @Suppress("DEPRECATION") // We don't have an alternative API to use + val wasAccessible = field.isAccessible + field.isAccessible = true + + return try { + @Suppress("UNCHECKED_CAST") + (field.get(this) as? Map).orEmpty() + } catch (e: IllegalAccessException) { + logger.warn("Error while retrieving LaF", e) + emptyMap() + } finally { + field.isAccessible = wasAccessible + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e3ec89387..9998e45099 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,11 +32,7 @@ plugins { include( ":ui", ":foundation", - ":decorated-window", - ":int-ui:int-ui-decorated-window", - ":int-ui:int-ui-standalone", ":ide-laf-bridge", - ":samples:standalone", ":samples:ide-plugin", )