Skip to content

Commit

Permalink
Add a customizable circular progress to the UI
Browse files Browse the repository at this point in the history
This commit adds a flexible circular progress option to the UI that supports both small and large size variants. The base theme has been updated to provide styling to both variants, and new SVG files have been added to provide dynamic views of the circular progress. The circular progress can be incorporated in the UI using the CircularProgress and CircularProgressBig components. This addition provides a more visually engaging way to show progress in the UI.
  • Loading branch information
fscarponi committed Sep 26, 2023
1 parent ef2c84e commit c5ddcf9
Show file tree
Hide file tree
Showing 42 changed files with 714 additions and 47 deletions.
21 changes: 13 additions & 8 deletions core/src/main/kotlin/org/jetbrains/jewel/CircularProgressBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,34 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import org.jetbrains.jewel.styling.CircularProgressStyle

@Composable
fun CircularProgress(
modifier: Modifier,
modifier: Modifier = Modifier,
style: CircularProgressStyle = IntelliJTheme.circularProgressStyle,
) {
var currentFrame by remember { mutableStateOf(0) }
var currentFrame by remember { mutableStateOf(style.frameIcons.frames.first()) }

Icon(
painter = style.frameIcons.frames.value[currentFrame],
modifier = modifier,
painter = currentFrame.getPainter(LocalResourceLoader.current).value,
contentDescription = null,
modifier = modifier.size(style.metrics.size)
)

LaunchedEffect(Unit) {
delay(style.metrics.animationDelay.toMillis())
while (true) {
for (i in 0 until style.frameIcons.frames.value.size) {
currentFrame = i
delay(style.metrics.frameTime.toMillis())
for (i in 0 until style.frameIcons.frames.size) {
currentFrame = style.frameIcons.frames[i]
delay(style.frameTime.inWholeMilliseconds)
}
}
}
}

@Composable
fun CircularProgressBig() {
CircularProgress(modifier = Modifier.size(32.dp), style = IntelliJTheme.circularProgressBigStyle)
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class IntelliJComponentStyling(
val textAreaStyle: TextAreaStyle,
val textFieldStyle: TextFieldStyle,
val circularProgressStyle: CircularProgressStyle,
val circularProgressBigStyle: CircularProgressStyle,
) {

override fun equals(other: Any?): Boolean {
Expand All @@ -64,6 +65,7 @@ class IntelliJComponentStyling(
if (defaultTabStyle != other.defaultTabStyle) return false
if (editorTabStyle != other.editorTabStyle) return false
if (circularProgressStyle != other.circularProgressStyle) return false
if (circularProgressBigStyle != other.circularProgressBigStyle) return false

return true
}
Expand All @@ -87,6 +89,7 @@ class IntelliJComponentStyling(
result = 31 * result + defaultTabStyle.hashCode()
result = 31 * result + editorTabStyle.hashCode()
result = 31 * result + circularProgressStyle.hashCode()
result = 31 * result + circularProgressBigStyle.hashCode()
return result
}

Expand All @@ -98,5 +101,5 @@ class IntelliJComponentStyling(
"lazyTreeStyle=$lazyTreeStyle, linkStyle=$linkStyle, menuStyle=$menuStyle, " +
"outlinedButtonStyle=$outlinedButtonStyle, radioButtonStyle=$radioButtonStyle, " +
"scrollbarStyle=$scrollbarStyle, textAreaStyle=$textAreaStyle, textFieldStyle=$textFieldStyle" +
"circularProgressStyle=$circularProgressStyle)"
"circularProgressStyle=$circularProgressStyle), circularProgressBigStyle=$circularProgressBigStyle)"
}
5 changes: 5 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ interface IntelliJTheme {
@Composable
@ReadOnlyComposable
get() = LocalCircularProgressStyle.current

val circularProgressBigStyle: CircularProgressStyle
@Composable
@ReadOnlyComposable
get() = LocalCircularProgressStyle.current
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
package org.jetbrains.jewel.styling

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.DpSize
import kotlin.time.Duration

interface CircularProgressStyle {

val frameIcons: CircularProgressIcons
val metrics: CircularProgressMetrics
val frameTime: Duration
}

@Immutable
interface CircularProgressIcons {

val frames: State<List<Painter>>
val frames: List<PainterProvider<Unit>>
}

@Immutable
interface CircularProgressMetrics {

val animationDelay: Duration
val frameTime: Duration
val size: DpSize
val LocalCircularProgressStyle = staticCompositionLocalOf<CircularProgressStyle> {
error("No CircularProgressStyle provided")
}

val LocalCircularProgressStyle = staticCompositionLocalOf<CircularProgressStyle> {
val LocalCircularProgressBigStyle = staticCompositionLocalOf<CircularProgressStyle> {
error("No CircularProgressStyle provided")
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ 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.IntUiCircularProgressIcons
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
Expand Down Expand Up @@ -160,7 +162,8 @@ internal fun createSwingIntUiComponentStyling(
radioButtonStyle = readRadioButtonStyle(theme.iconData, svgLoader),
scrollbarStyle = readScrollbarStyle(theme.isDark),
textAreaStyle = readTextAreaStyle(textAreaTextStyle, textFieldStyle.metrics),
circularProgressStyle = readCircularProgressStyle(theme.isDark, svgLoader),
circularProgressStyle = readCircularProgressStyle(svgLoader, theme.iconData),
circularProgressBigStyle = readCircularProgressBigStyle(svgLoader, theme.iconData),
textFieldStyle = textFieldStyle,
)
}
Expand Down Expand Up @@ -883,4 +886,42 @@ private fun readEditorTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLo
)
}

private fun readCircularProgressStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiCircula
private fun readCircularProgressStyle(
svgLoader: SvgLoader,
iconData: IntelliJThemeIconData,
): IntUiCircularProgressStyle =
IntUiCircularProgressStyle(
frameTime = 125.milliseconds,
frameIcons = IntUiCircularProgressIcons(
listOf(
retrieveStatelessIcon("process/step_1.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_2.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_3.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_4.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_5.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_6.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_7.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/step_8.svg", svgLoader = svgLoader, iconData = iconData),
),
),
)

private fun readCircularProgressBigStyle(
svgLoader: SvgLoader,
iconData: IntelliJThemeIconData,
): IntUiCircularProgressStyle =
IntUiCircularProgressStyle(
frameTime = 125.milliseconds,
frameIcons = IntUiCircularProgressIcons(
listOf(
retrieveStatelessIcon("process/big/step_1.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_2.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_3.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_4.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_5.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_6.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_7.svg", svgLoader = svgLoader, iconData = iconData),
retrieveStatelessIcon("process/big/step_8.svg", svgLoader = svgLoader, iconData = iconData),
),
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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.LocalCircularProgressBigStyle
import org.jetbrains.jewel.styling.LocalCircularProgressStyle
import org.jetbrains.jewel.styling.LocalDefaultButtonStyle
import org.jetbrains.jewel.styling.LocalDefaultTabStyle
Expand Down Expand Up @@ -229,6 +230,7 @@ fun BaseIntUiTheme(
LocalEditorTabStyle provides componentStyling.editorTabStyle,
LocalIndication provides NoIndication,
LocalCircularProgressStyle provides componentStyling.circularProgressStyle,
LocalCircularProgressBigStyle provides componentStyling.circularProgressBigStyle,
) {
IntelliJTheme(theme, swingCompatMode, content)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -128,6 +130,8 @@ object IntUiTheme : BaseIntUiTheme {
lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.dark(svgLoader),
defaultTabStyle: TabStyle = IntUiTabStyle.Default.dark(svgLoader),
editorTabStyle: TabStyle = IntUiTabStyle.Editor.dark(svgLoader),
circularProgressStyle: CircularProgressStyle = IntUiCircularProgressStyle.Small.dark(svgLoader),
circularProgressBigStyle: CircularProgressStyle = IntUiCircularProgressStyle.Big.dark(svgLoader),
) =
IntelliJComponentStyling(
checkboxStyle = checkboxStyle,
Expand All @@ -147,6 +151,8 @@ object IntUiTheme : BaseIntUiTheme {
scrollbarStyle = scrollbarStyle,
textAreaStyle = textAreaStyle,
textFieldStyle = textFieldStyle,
circularProgressStyle = circularProgressStyle,
circularProgressBigStyle = circularProgressBigStyle,
)

@Composable
Expand All @@ -169,26 +175,29 @@ 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.Small.light(svgLoader),
circularProgressBigStyle: CircularProgressStyle = IntUiCircularProgressStyle.Big.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,
circularProgressBigStyle = circularProgressBigStyle,
)
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.jetbrains.jewel.intui.standalone.styling

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import org.jetbrains.jewel.SvgLoader
import org.jetbrains.jewel.styling.CircularProgressIcons
import org.jetbrains.jewel.styling.CircularProgressStyle
import org.jetbrains.jewel.styling.PainterProvider
import org.jetbrains.jewel.styling.ResourcePainterProvider
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

@Stable
data class IntUiCircularProgressStyle(
override val frameIcons: IntUiCircularProgressIcons,
override val frameTime: Duration,
) : CircularProgressStyle {

object Small {

@Composable
fun dark(
svgLoader: SvgLoader,
frameTime: Duration = 125.milliseconds,
frameIcons: IntUiCircularProgressIcons = intUiCircularProgressIcons(svgLoader),
) =
IntUiCircularProgressStyle(frameIcons, frameTime)

@Composable
fun light(
svgLoader: SvgLoader,
frameTime: Duration = 125.milliseconds,
frameIcons: IntUiCircularProgressIcons = intUiCircularProgressIcons(svgLoader),
) =
IntUiCircularProgressStyle(frameIcons, frameTime)
}

object Big {

@Composable
fun dark(
svgLoader: SvgLoader,
frameTime: Duration = 125.milliseconds,
frameIcons: IntUiCircularProgressIcons = intUiCircularProgressBigIcons(svgLoader),
) =
IntUiCircularProgressStyle(frameIcons, frameTime)

@Composable
fun light(
svgLoader: SvgLoader,
frameTime: Duration = 125.milliseconds,
frameIcons: IntUiCircularProgressIcons = intUiCircularProgressBigIcons(svgLoader),
) =
IntUiCircularProgressStyle(frameIcons, frameTime)
}
}

data class IntUiCircularProgressIcons(
override val frames: List<PainterProvider<Unit>>,
) : CircularProgressIcons {

companion object {

@Composable
fun getFrames(
svgLoader: SvgLoader,
iconsPaths: List<String>,
): List<PainterProvider<Unit>> = iconsPaths.map {
ResourcePainterProvider.stateless(basePath = it, svgLoader = svgLoader)
}
}
}

@Composable
fun intUiCircularProgressIcons(
svgLoader: SvgLoader,
iconPrefix: String = "icons/intui/animated/smallSpinner/",
iconsNames: List<String> = listOf(
"spinner1.svg",
"spinner2.svg",
"spinner3.svg",
"spinner4.svg",
"spinner5.svg",
"spinner6.svg",
"spinner7.svg",
"spinner8.svg",
),
) = IntUiCircularProgressIcons(
IntUiCircularProgressIcons.getFrames(
svgLoader,
iconsNames.map { iconPrefix + it },
),
)

@Composable
fun intUiCircularProgressBigIcons(svgLoader: SvgLoader) =
intUiCircularProgressIcons(svgLoader = svgLoader, iconPrefix = "icons/intui/animated/bigSpinner/")
Loading

0 comments on commit c5ddcf9

Please sign in to comment.