Browse Source

Intellij plugin to choose color (truth of concept) (#1990)

feature/multiplatform-library-template
dima-avdeev-jb 2 years ago committed by GitHub
parent
commit
e4cebf9eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts
  2. 120
      examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt
  3. 128
      examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt
  4. 75
      examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt
  5. 5
      examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml
  6. 25
      examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt
  7. 22
      examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt

11
examples/intellij-plugin-with-experimental-shared-base/build.gradle.kts

@ -18,17 +18,18 @@ repositories {
}
dependencies {
// runtime dependency is provided by org.jetbrains.compose.intellij.platform
compileOnly(compose.desktop.currentOs)
testImplementation("junit", "junit", "4.12")
// compileOnly(compose.desktop.currentOs) runtime dependency is provided by org.jetbrains.compose.intellij.platform
testImplementation(kotlin("test"))
}
// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
version.set("2021.3")
plugins.set(
listOf("org.jetbrains.compose.intellij.platform:0.1.0")
listOf(
"org.jetbrains.compose.intellij.platform:0.1.0",
"org.jetbrains.kotlin"
)
)
}

120
examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorLineMarkerProvider.kt

@ -0,0 +1,120 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.jetbrains.compose.color
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.ComposePanel
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.icons.AllIcons
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.uast.*
import javax.swing.JComponent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.toArgb
import com.intellij.openapi.application.ApplicationManager
import com.jetbrains.compose.theme.WidgetTheme
import org.intellij.datavis.r.inlays.components.GraphicsManager
import java.awt.Component
import java.awt.Graphics
import javax.swing.Icon
class ColorLineMarkerProvider : LineMarkerProvider {
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
val project = element.project
val ktPsiFactory = KtPsiFactory(project)
val uElement: UElement = element.toUElement() ?: return null
if (uElement is UCallExpression) {
if (uElement.kind == UastCallKind.METHOD_CALL && uElement.methodIdentifier?.name == "Color") {
val colorLongValue = (uElement.valueArguments.firstOrNull() as? ULiteralExpression)?.getLongValue()
val previousColor = try {
Color(colorLongValue!!)
} catch (t: Throwable) {
Color(0xffffffff)
}
val iconSize = 20
return LineMarkerInfo(
element,
element.textRange,
object : Icon {
override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) {
g?.color = java.awt.Color(
previousColor.red,
previousColor.green,
previousColor.blue,
previousColor.alpha
)
g?.fillRect(0, 0, iconSize, iconSize)
}
override fun getIconWidth(): Int = iconSize
override fun getIconHeight(): Int = iconSize
},
null,
{ _, psiElement: PsiElement ->
val isDarkMode = try {
GraphicsManager.getInstance(project)?.isDarkModeEnabled ?: false
} catch (t: Throwable) {
false
}
class ChooseColorDialog() : DialogWrapper(project) {
val colorState = mutableStateOf(previousColor)
init {
title = "Choose color"
init()
}
override fun createCenterPanel(): JComponent =
ComposePanel().apply {
setBounds(0, 0, 400, 400)
setContent {
WidgetTheme(darkTheme = isDarkMode) {
Surface(modifier = Modifier.fillMaxSize()) {
ColorPicker(colorState)
}
}
}
}
}
val chooseColorDialog = ChooseColorDialog()
val result = chooseColorDialog.showAndGet()
if (result) {
val color = chooseColorDialog.colorState.value
ApplicationManager.getApplication().runWriteAction {
psiElement.replace(
ktPsiFactory.createExpression(
"Color(${color.toHexString()})"
)
)
}
}
},
GutterIconRenderer.Alignment.RIGHT,
{ "change color literal" }
)
}
}
return null
}
override fun collectSlowLineMarkers(
elements: MutableList<out PsiElement>,
result: MutableCollection<in LineMarkerInfo<*>>
) {
}
}

128
examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/ColorPicker.kt

@ -0,0 +1,128 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.jetbrains.compose.color
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.isPrimaryPressed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
private const val VALUE_BAND_RATIO = 0.07f
private val DEFAULT_COLORS =
listOf(Color.Red, Color.Green, Color.Blue, Color.Black, Color.Gray, Color.Yellow, Color.Cyan, Color.Magenta)
@Composable
fun ColorPicker(colorState: MutableState<Color>) {
var currentColor: Color by remember { colorState }
Column {
Row {
DEFAULT_COLORS.forEach {
Box(Modifier.size(30.dp).background(color = it).clickable {
currentColor = it
})
}
}
Divider(Modifier.size(2.dp))
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Result color:")
Divider(Modifier.size(2.dp))
TextField(modifier = Modifier.width(120f.dp), value = currentColor.toHexString(), onValueChange = {})
Divider(Modifier.size(2.dp))
val size = 60f
Box(Modifier.size(size.dp).background(color = currentColor))
}
Divider(Modifier.size(2.dp))
var width by remember { mutableStateOf(300) }
var height by remember { mutableStateOf(256) }
val rainbowWidth by derivedStateOf { (width * (1 - VALUE_BAND_RATIO)).toInt() }
val bandWidth by derivedStateOf { width * VALUE_BAND_RATIO }
fun calcHue(x: Float) = limit0to1(x / rainbowWidth) * HSV.HUE_MAX_VALUE
fun calcSaturation(y: Float) = 1 - limit0to1(y / height)
fun calcValue(y: Float) = 1 - limit0to1(y / height)
Row(Modifier.fillMaxSize()) {
Canvas(Modifier.fillMaxSize().pointerInput(Unit) {
width = size.width
height = size.height
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
if (event.buttons.isPrimaryPressed) {
val position = event.changes.first().position
if (position.x < rainbowWidth) {
currentColor = try {
currentColor.toHsv().copy(
hue = calcHue(position.x),
saturation = calcSaturation(position.y)
).toRgb()
} catch (t: Throwable) {
t.printStackTrace()
println("exception $t")
currentColor
}
} else {
currentColor =
currentColor.toHsv().copy(
value = calcValue(position.y)
).toRgb()
}
}
}
}
}) {
for (x in 0..rainbowWidth) {
for (y in 0..height) {
drawRect(
color = currentColor.toHsv().copy(
hue = calcHue(x.toFloat()),
saturation = calcSaturation(y.toFloat())
).toRgb(),
topLeft = Offset(x.toFloat(), y.toFloat()),
size = Size(1f, 1f)
)
}
}
val valueBandX = rainbowWidth + 1
for (y in 0..height) {
drawRect(
color = currentColor.toHsv().copy(value = calcValue(y.toFloat())).toRgb(),
topLeft = Offset(valueBandX.toFloat(), y.toFloat()),
size = Size(bandWidth, 1f)
)
}
val circleX = (currentColor.toHsv().hue / 360) * rainbowWidth
val circleY = (1 - currentColor.toHsv().saturation) * height
drawCircle(
center = Offset(circleX, circleY),
color = Color.Black,
radius = 5f,
style = Stroke(width = 3f)
)
}
}
}
}
fun Color.toHexString() = "0x" + toArgb().toUInt().toString(16)
fun limit(value: Float, min: Float, max: Float) = minOf(
maxOf(value, min),
max
)
fun limit0to1(value: Float) = limit(value = value, 0f, 1f)

75
examples/intellij-plugin-with-experimental-shared-base/src/main/kotlin/com/jetbrains/compose/color/HSV.kt

@ -0,0 +1,75 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.jetbrains.compose.color
import androidx.compose.ui.graphics.Color
import kotlin.math.abs
data class HSV(
/**
* 0.0 .. 360.0
*/
val hue: Float,
/**
* 0.0 .. 1.0
*/
val saturation: Float,
/**
* 0.0 . 1.0¬
*/
val value: Float
) {
companion object {
const val HUE_MAX_VALUE = 360f
}
}
/**
* Convert to HSV color space
* https://www.rapidtables.com/convert/color/rgb-to-hsv.html
*/
fun Color.toHsv(): HSV {
val max = maxOf(red, green, blue)
val min = minOf(red, green, blue)
val delta = max - min
val h = when {
delta == 0f -> 0f
max == red -> 60 * ((green - blue) / delta).mod(6f)
max == green -> 60 * ((blue - red) / delta + 2)
max == blue -> 60 * ((red - green) / delta + 4)
else -> 0f
}
val s = when {
max == 0f -> 0f
else -> delta / max
}
val v = max
return HSV(
hue = h,
saturation = s,
value = v
)
}
/**
* Convert to RGB color space
* https://www.rapidtables.com/convert/color/hsv-to-rgb.html
*/
fun HSV.toRgb(): Color {
val c = value * saturation
val x = minOf(c * (1 - abs((hue / 60).mod(2f) - 1)), 1f)
val m = value - c
val tempColor = when {
hue >= 0 && hue < 60 -> Color(c, x, 0f)
hue >= 60 && hue < 120 -> Color(x, c, 0f)
hue >= 120 && hue < 180 -> Color(0f, c, x)
hue >= 180 && hue < 240 -> Color(0f, x, c)
hue >= 240 && hue < 300 -> Color(x, 0f, c)
else -> Color(c, 0f, x)
}
return Color(minOf(m + tempColor.red, 1f), minOf(m + tempColor.green, 1f), minOf(m + tempColor.blue, 1f))
}

5
examples/intellij-plugin-with-experimental-shared-base/src/main/resources/META-INF/plugin.xml

@ -11,6 +11,7 @@ A plugin demonstrates Jetpack compose capabilities on IntelliJ Platform
on how to target different products -->
<depends>com.intellij.modules.platform</depends>
<depends>org.jetbrains.compose.intellij.platform</depends>
<depends>org.jetbrains.kotlin</depends>
<actions>
<!-- Add your actions here -->
@ -19,4 +20,8 @@ A plugin demonstrates Jetpack compose capabilities on IntelliJ Platform
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
</actions>
<extensions defaultExtensionNs="com.intellij">
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="com.jetbrains.compose.color.ColorLineMarkerProvider" />
</extensions>
</idea-plugin>

25
examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/ColorPickerUITest.kt

@ -0,0 +1,25 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.jetbrains.compose.color
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
fun main() = application {
val windowState = remember { WindowState(width = 400.dp, height = 400.dp) }
Window(
onCloseRequest = ::exitApplication,
title = "ColorPicker",
state = windowState
) {
ColorPicker(mutableStateOf(Color(0xffaabbcc)))
}
}

22
examples/intellij-plugin-with-experimental-shared-base/src/test/kotlin/com/jetbrains/compose/color/HSVTest.kt

@ -0,0 +1,22 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.jetbrains.compose.color
import androidx.compose.ui.graphics.Color
import org.junit.Test
import kotlin.test.assertEquals
class HSVTest {
@Test
fun testGreenToHsv() {
val greenRgb = Color(0xff00ff00)
val result = greenRgb.toHsv()
assertEquals(HSV(120f, 1f, 1f), result)
assertEquals(greenRgb, result.toRgb())
}
}
Loading…
Cancel
Save