diff --git a/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt b/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt new file mode 100644 index 0000000000..20f7063ef0 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt @@ -0,0 +1,170 @@ +package org.jetbrains.jewel + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.graphics.Color +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import org.jetbrains.jewel.styling.CircularProgressStyle +import org.jetbrains.jewel.util.toHexString + +@Composable +fun CircularProgressIndicator( + svgLoader: SvgLoader, + modifier: Modifier = Modifier, + style: CircularProgressStyle = IntelliJTheme.circularProgressStyle, +) { + CircularProgressIndicatorImpl( + modifier = modifier, + svgLoader = svgLoader, + iconSize = DpSize(16.dp, 16.dp), + style = style, + frameRetriever = { color -> SpinnerProgressIconGenerator.Small.generateSvgFrames(color.toHexString()) }, + ) +} + +@Composable +fun CircularProgressIndicatorBig( + svgLoader: SvgLoader, + modifier: Modifier = Modifier, + style: CircularProgressStyle = IntelliJTheme.circularProgressStyle, +) { + CircularProgressIndicatorImpl( + modifier = modifier, + svgLoader = svgLoader, + iconSize = DpSize(32.dp, 32.dp), + style = style, + frameRetriever = { color -> SpinnerProgressIconGenerator.Big.generateSvgFrames(color.toHexString()) }, + ) +} + +@Composable +private fun CircularProgressIndicatorImpl( + modifier: Modifier = Modifier, + svgLoader: SvgLoader, + iconSize: DpSize, + style: CircularProgressStyle, + frameRetriever: (Color) -> List, +) { + val defaultColor = if (IntelliJTheme.isDark) Color(0xFF6F737A) else Color(0xFFA8ADBD) + var isFrameReady by remember { mutableStateOf(false) } + var currentFrame: Pair by remember { mutableStateOf("" to 0) } + + if (!isFrameReady) { + Box(modifier.size(iconSize)) + } else { + Icon( + modifier = modifier.size(iconSize), + painter = svgLoader.loadRawSvg( + currentFrame.first, + "circularProgressIndicator_frame_${currentFrame.second}", + ), + contentDescription = null, + ) + } + + LaunchedEffect(style.color) { + val frames = frameRetriever(style.color.takeOrElse { defaultColor }) + while (true) { + for (i in 0 until frames.size) { + currentFrame = frames[i] to i + isFrameReady = true + delay(style.frameTime.inWholeMilliseconds) + } + } + } +} + +object SpinnerProgressIconGenerator { + + private val opacityList = listOf(1.0f, 0.93f, 0.78f, 0.69f, 0.62f, 0.48f, 0.38f, 0.0f) + + private fun StringBuilder.closeRoot() = append("") + private fun StringBuilder.openRoot(sizePx: Int) = append( + "", + ) + + private fun generateSvgIcon( + size: Int, + opacityListShifted: List, + colorHex: String, + ) = + buildString { + openRoot(size) + elements( + colorHex = colorHex, + opacityList = opacityListShifted, + ) + closeRoot() + } + + private fun StringBuilder.elements( + colorHex: String, + opacityList: List, + ) { + append( + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n", + ) + } + + object Small { + + fun generateSvgFrames(colorHex: String) = buildList { + val opacityListShifted = opacityList.toMutableList() + repeat(opacityList.count()) { + add( + generateSvgIcon( + size = 16, + colorHex = colorHex, + opacityListShifted = opacityListShifted, + ), + ) + opacityListShifted.shtr() + } + } + } + + object Big { + + fun generateSvgFrames(colorHex: String) = buildList { + val opacityListShifted = opacityList.toMutableList() + repeat(opacityList.count()) { + add( + generateSvgIcon( + size = 32, + colorHex = colorHex, + opacityListShifted = opacityListShifted, + ), + ) + opacityListShifted.shtr() + } + } + } + + private fun MutableList.shtr() { + add(first()) + removeFirst() + } +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt index ac4cbde9db..08a232f8fc 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle +import org.jetbrains.jewel.styling.CircularProgressStyle import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.GroupHeaderStyle import org.jetbrains.jewel.styling.HorizontalProgressBarStyle @@ -36,6 +37,7 @@ class IntelliJComponentStyling( val scrollbarStyle: ScrollbarStyle, val textAreaStyle: TextAreaStyle, val textFieldStyle: TextFieldStyle, + val circularProgressStyle: CircularProgressStyle, ) { override fun equals(other: Any?): Boolean { @@ -61,6 +63,7 @@ class IntelliJComponentStyling( if (lazyTreeStyle != other.lazyTreeStyle) return false if (defaultTabStyle != other.defaultTabStyle) return false if (editorTabStyle != other.editorTabStyle) return false + if (circularProgressStyle != other.circularProgressStyle) return false return true } @@ -83,6 +86,7 @@ class IntelliJComponentStyling( result = 31 * result + lazyTreeStyle.hashCode() result = 31 * result + defaultTabStyle.hashCode() result = 31 * result + editorTabStyle.hashCode() + result = 31 * result + circularProgressStyle.hashCode() return result } @@ -93,5 +97,6 @@ class IntelliJComponentStyling( "horizontalProgressBarStyle=$horizontalProgressBarStyle, labelledTextFieldStyle=$labelledTextFieldStyle, " + "lazyTreeStyle=$lazyTreeStyle, linkStyle=$linkStyle, menuStyle=$menuStyle, " + "outlinedButtonStyle=$outlinedButtonStyle, radioButtonStyle=$radioButtonStyle, " + - "scrollbarStyle=$scrollbarStyle, textAreaStyle=$textAreaStyle, textFieldStyle=$textFieldStyle)" + "scrollbarStyle=$scrollbarStyle, textAreaStyle=$textAreaStyle, textFieldStyle=$textFieldStyle" + + "circularProgressStyle=$circularProgressStyle)" } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt index 6c26b6eacd..e036ab87e8 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle +import org.jetbrains.jewel.styling.CircularProgressStyle import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.GroupHeaderStyle import org.jetbrains.jewel.styling.HorizontalProgressBarStyle @@ -17,6 +18,7 @@ import org.jetbrains.jewel.styling.LazyTreeStyle import org.jetbrains.jewel.styling.LinkStyle import org.jetbrains.jewel.styling.LocalCheckboxStyle import org.jetbrains.jewel.styling.LocalChipStyle +import org.jetbrains.jewel.styling.LocalCircularProgressStyle import org.jetbrains.jewel.styling.LocalDefaultButtonStyle import org.jetbrains.jewel.styling.LocalDefaultTabStyle import org.jetbrains.jewel.styling.LocalDropdownStyle @@ -175,6 +177,11 @@ interface IntelliJTheme { @Composable @ReadOnlyComposable get() = LocalEditorTabStyle.current + + val circularProgressStyle: CircularProgressStyle + @Composable + @ReadOnlyComposable + get() = LocalCircularProgressStyle.current } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/CircularProgressStyle.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/CircularProgressStyle.kt new file mode 100644 index 0000000000..ae3d34d423 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/CircularProgressStyle.kt @@ -0,0 +1,15 @@ +package org.jetbrains.jewel.styling + +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import kotlin.time.Duration + +interface CircularProgressStyle { + + val frameTime: Duration + val color: Color +} + +val LocalCircularProgressStyle = staticCompositionLocalOf { + error("No CircularProgressIndicatorStyle provided") +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt b/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt new file mode 100644 index 0000000000..f2da077e4d --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt @@ -0,0 +1,22 @@ +package org.jetbrains.jewel.util + +import androidx.compose.ui.graphics.Color +import kotlin.math.roundToInt + +fun Color.toHexString(): String { + val r = Integer.toHexString((red * 255).roundToInt()) + val g = Integer.toHexString((green * 255).roundToInt()) + val b = Integer.toHexString((blue * 255).roundToInt()) + + return buildString { + append('#') + append(r.padStart(2, '0')) + append(g.padStart(2, '0')) + append(b.padStart(2, '0')) + + if (alpha != 1.0f) { + val a = Integer.toHexString((alpha * 255).roundToInt()) + append(a.padStart(2, '0')) + } + } +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/util/SpinnerProgressIconGenerator.kt b/core/src/main/kotlin/org/jetbrains/jewel/util/SpinnerProgressIconGenerator.kt new file mode 100644 index 0000000000..578428e9ec --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/util/SpinnerProgressIconGenerator.kt @@ -0,0 +1,102 @@ +package org.jetbrains.jewel.util + +object SpinnerProgressIconGenerator { + + private val opacityList = listOf(1.0f, 0.93f, 0.78f, 0.69f, 0.62f, 0.48f, 0.38f, 0.0f) + + private val rotations = listOf(0, -45, 0, 45, 0, -45, 0, 45) + + // for a 16x16 icon + internal val points = listOf( + 7f to 1f, + 2.34961f to 3.76416f, + 1f to 7f, + 5.17871f to 9.40991f, + 7f to 11f, + 9.41016f to 10.8242f, + 11f to 7f, + 12.2383f to 2.34961f, + ) + + private fun StringBuilder.closeTag() = append("") + private fun StringBuilder.openTag(sizePx: Int) = append( + "", + ) + + private fun getSvgPlainTextIcon( + step: Int, + pointList: List>, + colorHex: String, + thickness: Int = 2, + length: Int = 4, + cornerRadius: Int = 1, + ) = + buildString { + openTag(16) + appendLine() + for (index in 0..opacityList.lastIndex) { + val currentIndex = (index + step + 1) % opacityList.size + val currentOpacity = opacityList[currentIndex] + if (currentOpacity == 0.0f) continue + drawElement( + colorHex = colorHex, + opacity = currentOpacity, + x = pointList[index].first, + y = pointList[index].second, + width = thickness, + height = length, + rx = cornerRadius, + rotation = rotations[index], + ) + } + closeTag() + appendLine() + } + + private fun StringBuilder.drawElement( + colorHex: String, + opacity: Float, + x: Float, + y: Float, + width: Int, + height: Int, + rx: Int, + rotation: Int, + ) { + append("\n") + } + + internal fun getPlainTextSvgList(colorHex: String, size: Int) = buildList { + val scaleFactor = size / 16f + for (index in 0..opacityList.lastIndex) { + if (size == 16) { + add(getSvgPlainTextIcon(index, points, colorHex)) + } else { + add( + getSvgPlainTextIcon( + index, + points.map { it.first * scaleFactor to it.second * scaleFactor }, + colorHex, + thickness = (2 * scaleFactor).toInt().coerceAtLeast(1), + length = (4 * scaleFactor).toInt().coerceAtLeast(1), + cornerRadius = (2 * scaleFactor).toInt().coerceAtLeast(1), + ), + ) + } + } + } + + object Small { + + fun generateRawSvg(colorHex: String) = getPlainTextSvgList(colorHex = colorHex, size = 16) + } + + object Big { + + fun generateRawSvg(colorHex: String) = + getPlainTextSvgList(colorHex = colorHex, size = 32) + } +} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt index 3fd20bdf18..c3692af662 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt @@ -43,10 +43,20 @@ fun java.awt.Color.toComposeColor() = Color( fun java.awt.Color?.toComposeColorOrUnspecified() = this?.toComposeColor() ?: Color.Unspecified +@Suppress("JavaIoSerializableObjectMustHaveReadResolve") +private object FallbackMarker : JBColor(0x0000, 0x0000) { + + override fun toString() = "%%%%%%COLOR_NOT_FOUND_MARKER%%%%%%" + + override fun equals(other: Any?) = other === this + + override fun hashCode() = toString().hashCode() +} + @Suppress("UnstableApiUsage") -fun retrieveColorOrNull(key: String) = - JBColor.namedColor(key) - .takeUnless { it.name == "NAMED_COLOR_FALLBACK_MARKER" } +fun retrieveColorOrNull(key: String): Color? = + JBColor.namedColor(key, FallbackMarker) + .takeUnless { it.name == "NAMED_COLOR_FALLBACK_MARKER" || it == FallbackMarker } ?.toComposeColor() fun retrieveColorOrUnspecified(key: String): Color { diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt index 62ff507fef..1a6bf9c970 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.shape.CornerSize import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.text.TextStyle @@ -36,6 +37,7 @@ import org.jetbrains.jewel.intui.standalone.styling.IntUiCheckboxStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiChipColors import org.jetbrains.jewel.intui.standalone.styling.IntUiChipMetrics import org.jetbrains.jewel.intui.standalone.styling.IntUiChipStyle +import org.jetbrains.jewel.intui.standalone.styling.IntUiCircularProgressStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownColors import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownIcons import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownMetrics @@ -160,6 +162,7 @@ internal fun createSwingIntUiComponentStyling( radioButtonStyle = readRadioButtonStyle(theme.iconData, svgLoader), scrollbarStyle = readScrollbarStyle(theme.isDark), textAreaStyle = readTextAreaStyle(textAreaTextStyle, textFieldStyle.metrics), + circularProgressStyle = readCircularProgressStyle(theme.isDark), textFieldStyle = textFieldStyle, ) } @@ -881,3 +884,13 @@ private fun readEditorTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLo ), ) } + +private fun readCircularProgressStyle( + isDark: Boolean, +): IntUiCircularProgressStyle = + IntUiCircularProgressStyle( + frameTime = 125.milliseconds, + color = retrieveColorOrUnspecified("ProgressIcon.color") + .takeIf { it.isSpecified } + ?: if (isDark) Color(0xFF6F737A) else Color(0xFFA8ADBD), + ) diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt index 478bbc0c1f..c3acb83a7e 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt @@ -19,6 +19,7 @@ import org.jetbrains.jewel.NoIndication import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle +import org.jetbrains.jewel.styling.CircularProgressStyle import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.GroupHeaderStyle import org.jetbrains.jewel.styling.HorizontalProgressBarStyle @@ -27,6 +28,7 @@ import org.jetbrains.jewel.styling.LazyTreeStyle import org.jetbrains.jewel.styling.LinkStyle import org.jetbrains.jewel.styling.LocalCheckboxStyle import org.jetbrains.jewel.styling.LocalChipStyle +import org.jetbrains.jewel.styling.LocalCircularProgressStyle import org.jetbrains.jewel.styling.LocalDefaultButtonStyle import org.jetbrains.jewel.styling.LocalDefaultTabStyle import org.jetbrains.jewel.styling.LocalDropdownStyle @@ -178,6 +180,11 @@ interface BaseIntUiTheme : IntelliJTheme { @Composable @ReadOnlyComposable get() = IntelliJTheme.editorTabStyle + + val circularProgressStyle: CircularProgressStyle + @Composable + @ReadOnlyComposable + get() = IntelliJTheme.circularProgressStyle } @Composable @@ -218,6 +225,7 @@ fun BaseIntUiTheme( LocalDefaultTabStyle provides componentStyling.defaultTabStyle, LocalEditorTabStyle provides componentStyling.editorTabStyle, LocalIndication provides NoIndication, + LocalCircularProgressStyle provides componentStyling.circularProgressStyle, ) { IntelliJTheme(theme, swingCompatMode, content) } diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt index 7ca6db7f54..dfa88a4a66 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color import org.jetbrains.jewel.PaletteMapper import org.jetbrains.jewel.SvgPatcher +import org.jetbrains.jewel.util.toHexString import org.w3c.dom.Document import org.w3c.dom.Element import java.io.IOException @@ -63,24 +64,6 @@ class IntelliJSvgPatcher(private val mapper: PaletteMapper) : SvgPatcher { } } - private fun Color.toHexString(): String { - val r = Integer.toHexString((red * 255).roundToInt()) - val g = Integer.toHexString((green * 255).roundToInt()) - val b = Integer.toHexString((blue * 255).roundToInt()) - - return buildString { - append('#') - append(r.padStart(2, '0')) - append(g.padStart(2, '0')) - append(b.padStart(2, '0')) - - if (alpha != 1.0f) { - val a = Integer.toHexString((alpha * 255).roundToInt()) - append(a.padStart(2, '0')) - } - } - } - private fun tryParseColor(color: String, alpha: Float): Color? { val rawColor = color.lowercase() if (rawColor.startsWith("#") && rawColor.length - 1 <= 8) { diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt index cd5776461f..a7a9c6e4a5 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt @@ -34,6 +34,7 @@ import org.jetbrains.jewel.intui.standalone.IntUiTheme.defaultComponentStyling import org.jetbrains.jewel.intui.standalone.styling.IntUiButtonStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiCheckboxStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiChipStyle +import org.jetbrains.jewel.intui.standalone.styling.IntUiCircularProgressStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiGroupHeaderStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiHorizontalProgressBarStyle @@ -49,6 +50,7 @@ import org.jetbrains.jewel.intui.standalone.styling.IntUiTextFieldStyle import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle +import org.jetbrains.jewel.styling.CircularProgressStyle import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.GroupHeaderStyle import org.jetbrains.jewel.styling.HorizontalProgressBarStyle @@ -128,6 +130,7 @@ object IntUiTheme : BaseIntUiTheme { lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.dark(svgLoader), defaultTabStyle: TabStyle = IntUiTabStyle.Default.dark(svgLoader), editorTabStyle: TabStyle = IntUiTabStyle.Editor.dark(svgLoader), + circularProgressStyle: CircularProgressStyle = IntUiCircularProgressStyle.dark(), ) = IntelliJComponentStyling( checkboxStyle = checkboxStyle, @@ -147,6 +150,7 @@ object IntUiTheme : BaseIntUiTheme { scrollbarStyle = scrollbarStyle, textAreaStyle = textAreaStyle, textFieldStyle = textFieldStyle, + circularProgressStyle = circularProgressStyle, ) @Composable @@ -169,26 +173,27 @@ object IntUiTheme : BaseIntUiTheme { lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.light(svgLoader), defaultTabStyle: TabStyle = IntUiTabStyle.Default.light(svgLoader), editorTabStyle: TabStyle = IntUiTabStyle.Editor.light(svgLoader), - ) = - IntelliJComponentStyling( - checkboxStyle = checkboxStyle, - chipStyle = chipStyle, - defaultButtonStyle = defaultButtonStyle, - defaultTabStyle = defaultTabStyle, - dropdownStyle = dropdownStyle, - editorTabStyle = editorTabStyle, - groupHeaderStyle = groupHeaderStyle, - horizontalProgressBarStyle = horizontalProgressBarStyle, - labelledTextFieldStyle = labelledTextFieldStyle, - lazyTreeStyle = lazyTreeStyle, - linkStyle = linkStyle, - menuStyle = menuStyle, - outlinedButtonStyle = outlinedButtonStyle, - radioButtonStyle = radioButtonStyle, - scrollbarStyle = scrollbarStyle, - textAreaStyle = textAreaStyle, - textFieldStyle = textFieldStyle, - ) + circularProgressStyle: CircularProgressStyle = IntUiCircularProgressStyle.light(), + ) = IntelliJComponentStyling( + checkboxStyle = checkboxStyle, + chipStyle = chipStyle, + defaultButtonStyle = defaultButtonStyle, + defaultTabStyle = defaultTabStyle, + dropdownStyle = dropdownStyle, + editorTabStyle = editorTabStyle, + groupHeaderStyle = groupHeaderStyle, + horizontalProgressBarStyle = horizontalProgressBarStyle, + labelledTextFieldStyle = labelledTextFieldStyle, + lazyTreeStyle = lazyTreeStyle, + linkStyle = linkStyle, + menuStyle = menuStyle, + outlinedButtonStyle = outlinedButtonStyle, + radioButtonStyle = radioButtonStyle, + scrollbarStyle = scrollbarStyle, + textAreaStyle = textAreaStyle, + textFieldStyle = textFieldStyle, + circularProgressStyle = circularProgressStyle, + ) } @Composable diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCircularProgressStyle.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCircularProgressStyle.kt new file mode 100644 index 0000000000..20d99bb970 --- /dev/null +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCircularProgressStyle.kt @@ -0,0 +1,27 @@ +package org.jetbrains.jewel.intui.standalone.styling + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color +import org.jetbrains.jewel.styling.CircularProgressStyle +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +@Stable +data class IntUiCircularProgressStyle( + override val frameTime: Duration, + override val color: Color, +) : CircularProgressStyle { + + companion object { + + fun dark( + frameTime: Duration = 125.milliseconds, + color: Color = Color(0xFF6F737A), + ) = IntUiCircularProgressStyle(frameTime, color) + + fun light( + frameTime: Duration = 125.milliseconds, + color: Color = Color(0xFFA8ADBD), + ) = IntUiCircularProgressStyle(frameTime, color) + } +} diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/JewelDemoToolWindow.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/JewelDemoToolWindow.kt index f4535ce8b5..9de08e045c 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/JewelDemoToolWindow.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/JewelDemoToolWindow.kt @@ -32,6 +32,8 @@ import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.JBColor import kotlinx.coroutines.ExperimentalCoroutinesApi import org.jetbrains.jewel.CheckboxRow +import org.jetbrains.jewel.CircularProgressIndicator +import org.jetbrains.jewel.CircularProgressIndicatorBig import org.jetbrains.jewel.DefaultButton import org.jetbrains.jewel.ExperimentalJewelApi import org.jetbrains.jewel.Icon @@ -81,6 +83,8 @@ internal class JewelDemoToolWindow : ToolWindowFactory, DumbAware { Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp), ) { + val svgLoader = service().svgLoader + Text("Here is a selection of our finest components:") Row( @@ -132,11 +136,19 @@ internal class JewelDemoToolWindow : ToolWindowFactory, DumbAware { } Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - val svgLoader = service().svgLoader val painterProvider = retrieveStatelessIcon("actions/close.svg", svgLoader, IntUiTheme.iconData) val painter by painterProvider.getPainter(resourceLoader) Icon(painter = painter, modifier = Modifier.border(1.dp, Color.Magenta), contentDescription = "An icon") } + + Row { + Text("Circular progress small: ") + CircularProgressIndicator(svgLoader) + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Circular progress big: ") + CircularProgressIndicatorBig(svgLoader) + } } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index 23e1cf70d2..1b5f3fcf1f 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -89,7 +89,6 @@ fun main() { @Composable private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: ResourceLoader) { val verticalScrollState = rememberScrollState() - Box(Modifier.fillMaxSize()) { Column( Modifier.width(IntrinsicSize.Max) @@ -106,7 +105,7 @@ private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: Resourc Links() TextFields() TextAreas() - ProgressBar() + ProgressBar(svgLoader) ChipsAndTree() Tabs(svgLoader, resourceLoader) Icons(svgLoader, resourceLoader) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt index ae8d6d7787..89e944f0d6 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt @@ -14,16 +14,20 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay +import org.jetbrains.jewel.CircularProgressIndicator +import org.jetbrains.jewel.CircularProgressIndicatorBig import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.HorizontalProgressBar import org.jetbrains.jewel.IndeterminateHorizontalProgressBar +import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.Text @Composable -fun ProgressBar() { +fun ProgressBar(svgLoader: SvgLoader) { GroupHeader("Progress bars") val transition = rememberInfiniteTransition() val currentOffset by transition.animateFloat( @@ -50,21 +54,33 @@ fun ProgressBar() { } Column { Text("HorizontalProgressBar - linear progress") - Row(Modifier.width(600.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { HorizontalProgressBar(modifier = Modifier.width(500.dp), progress = currentOffset) Text("${(currentOffset * 100).toInt()} %") } } Column { Text("HorizontalProgressBar - non linear progress") - Row(Modifier.width(600.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { HorizontalProgressBar(modifier = Modifier.width(500.dp), progress = intermittentProgress) Text("${(intermittentProgress * 100).toInt()} %") } } Column { Text("HorizontalProgressBar - smoothed non linear progress") - Row(Modifier.width(600.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { val smoothedProgress by androidx.compose.animation.core.animateFloatAsState(intermittentProgress) HorizontalProgressBar(modifier = Modifier.width(500.dp), progress = smoothedProgress) Text("${(intermittentProgress * 100).toInt()} %") @@ -72,9 +88,31 @@ fun ProgressBar() { } Column { Text("IndeterminateHorizontalProgressBar") - Row(Modifier.width(600.dp), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { IndeterminateHorizontalProgressBar(modifier = Modifier.width(500.dp)) Text("----") } } + Column { + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text("CircularProgress (16x16)") + CircularProgressIndicator(svgLoader) + } + Row( + Modifier.width(600.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text("CircularProgressBig (32x32) - Big") + CircularProgressIndicatorBig(svgLoader) + } + } }