Skip to content

Commit

Permalink
Add custom tab styles and refine tab data structures
Browse files Browse the repository at this point in the history
This update implements support for custom tab styles in the Jewel UI theme. The changes encompass refinement of the TabData class and the introduction of Custom, ToolWindowTab, and Editor tab styles. These modifications aim at enhancing customization and versatility particularly for tab labels.
  • Loading branch information
fscarponi committed Dec 18, 2023
1 parent effad57 commit 2077d68
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons
Expand All @@ -23,30 +24,77 @@ import org.jetbrains.jewel.ui.component.IconButton
import org.jetbrains.jewel.ui.component.TabData
import org.jetbrains.jewel.ui.component.TabStrip
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.theme.defaultTabStyle
import org.jetbrains.jewel.ui.theme.editorTabStyle
import kotlin.math.max

@Composable
@View(title = "Tabs", position = 7)
fun Tabs() {
Text("Default tabs", Modifier.fillMaxWidth())
DefaultTabShowcase()
Text("Toolwindow tabs", Modifier.fillMaxWidth())
ToolwindowTabShowcase()

Spacer(Modifier.height(16.dp))
Text("Editor tabs", Modifier.fillMaxWidth())
EditorTabShowcase()

Spacer(Modifier.height(16.dp))
Text("Custom tabs", Modifier.fillMaxWidth())
CustomTabShowcase()
}

@Composable
fun CustomTabShowcase() {
var selectedTabIndex by remember { mutableStateOf(0) }

var tabIds by remember { mutableStateOf((1..12).toList()) }
val maxId = remember(tabIds) { tabIds.maxOrNull() ?: 0 }

val tabs = remember(tabIds, selectedTabIndex) {
tabIds.mapIndexed { index, id ->
TabData.Custom(
selected = index == selectedTabIndex,
content = {
val textColor = when {
it.isHovered -> Color.Red
else -> Color.Unspecified

}
Text(text = "Custom tab $id", color = textColor)

},
onClose = {
tabIds = tabIds.toMutableList().apply { removeAt(index) }
if (selectedTabIndex >= index) {
val maxPossibleIndex = max(0, tabIds.lastIndex)
selectedTabIndex = (selectedTabIndex - 1)
.coerceIn(0..maxPossibleIndex)
}
},
onClick = { selectedTabIndex = index },
)
}
}

TabStripWithAddButton(tabs) {
val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size)
val nextTabId = maxId + 1

tabIds = tabIds.toMutableList()
.apply { add(insertionIndex, nextTabId) }
selectedTabIndex = insertionIndex
}
}

@Composable
private fun DefaultTabShowcase() {
private fun ToolwindowTabShowcase() {
var selectedTabIndex by remember { mutableStateOf(0) }

var tabIds by remember { mutableStateOf((1..12).toList()) }
val maxId = remember(tabIds) { tabIds.maxOrNull() ?: 0 }

val tabs = remember(tabIds, selectedTabIndex) {
tabIds.mapIndexed { index, id ->
TabData.Default(
TabData.Default.ToolWindowTab(
selected = index == selectedTabIndex,
label = "Default tab $id",
onClose = {
Expand Down Expand Up @@ -81,7 +129,7 @@ private fun EditorTabShowcase() {

val tabs = remember(tabIds, selectedTabIndex) {
tabIds.mapIndexed { index, id ->
TabData.Editor(
TabData.Default.Editor(
selected = index == selectedTabIndex,
label = "Editor tab $id",
onClose = {
Expand Down Expand Up @@ -117,7 +165,7 @@ private fun TabStripWithAddButton(

IconButton(
onClick = onAddClick,
modifier = Modifier.size(JewelTheme.defaultTabStyle.metrics.tabHeight),
modifier = Modifier.size(JewelTheme.editorTabStyle.metrics.tabHeight),
) {
Icon(
resource = "expui/general/add.svg",
Expand Down
41 changes: 29 additions & 12 deletions ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.jetbrains.jewel.foundation.GenerateDataFunctions
import org.jetbrains.jewel.foundation.modifier.onHover
import org.jetbrains.jewel.foundation.state.CommonStateBitMask
import org.jetbrains.jewel.foundation.state.FocusableComponentState
import org.jetbrains.jewel.foundation.state.InteractiveComponentState

@Composable
public fun TabStrip(
Expand Down Expand Up @@ -81,28 +82,44 @@ public fun TabStrip(
public sealed class TabData {

public abstract val selected: Boolean
public abstract val label: String
public abstract val icon: Painter?
public abstract val closable: Boolean
public abstract val onClose: () -> Unit
public abstract val onClick: () -> Unit

@Immutable
@GenerateDataFunctions
public class Default(
override val selected: Boolean,
override val label: String,
override val icon: Painter? = null,
override val closable: Boolean = true,
override val onClose: () -> Unit = {},
override val onClick: () -> Unit = {},
) : TabData()
public sealed class Default : TabData() {

public abstract val label: String

@Immutable
@GenerateDataFunctions
public class ToolWindowTab(
override val selected: Boolean,
override val label: String,
override val icon: Painter? = null,
override val closable: Boolean = true,
override val onClose: () -> Unit = {},
override val onClick: () -> Unit = {},
) : Default()

@Immutable
@GenerateDataFunctions
public class Editor(
override val selected: Boolean,
override val label: String,
override val icon: Painter? = null,
override val closable: Boolean = true,
override val onClose: () -> Unit = {},
override val onClick: () -> Unit = {},
) : Default()
}

@Immutable
@GenerateDataFunctions
public class Editor(
public class Custom(
override val selected: Boolean,
override val label: String,
public val content: @Composable (tabState: InteractiveComponentState) -> Unit,
override val icon: Painter? = null,
override val closable: Boolean = true,
override val onClose: () -> Unit = {},
Expand Down
34 changes: 22 additions & 12 deletions ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Tabs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.LocalContentColor
import org.jetbrains.jewel.ui.NoIndication
import org.jetbrains.jewel.ui.painter.hints.Stateful
import org.jetbrains.jewel.ui.theme.defaultTabStyle
import org.jetbrains.jewel.ui.theme.customTabStyle
import org.jetbrains.jewel.ui.theme.editorTabStyle
import org.jetbrains.jewel.ui.theme.toolWindowTabStyle

@Composable
internal fun TabImpl(
Expand All @@ -57,8 +58,9 @@ internal fun TabImpl(
) {
val tabStyle =
when (tabData) {
is TabData.Default -> JewelTheme.defaultTabStyle
is TabData.Editor -> JewelTheme.editorTabStyle
is TabData.Default.ToolWindowTab -> JewelTheme.toolWindowTabStyle
is TabData.Default.Editor -> JewelTheme.editorTabStyle
is TabData.Custom -> JewelTheme.customTabStyle
}

var tabState by remember {
Expand Down Expand Up @@ -127,17 +129,25 @@ internal fun TabImpl(
Image(modifier = Modifier.alpha(iconAlpha), painter = icon, contentDescription = null)
}

Text(
modifier = Modifier.alpha(labelAlpha),
text = tabData.label,
color = tabStyle.colors.contentFor(tabState).value,
)
when (tabData) {
is TabData.Custom -> {
tabData.content(tabState)
}

is TabData.Default -> {
Text(
modifier = Modifier.alpha(labelAlpha),
text = tabData.label,
color = tabStyle.colors.contentFor(tabState).value,
)
}
}

val showCloseIcon =
when (tabData) {
is TabData.Default -> tabData.closable
is TabData.Editor -> tabData.closable && (tabState.isHovered || tabState.isSelected)
is TabData.Default.ToolWindowTab -> tabData.closable
is TabData.Default.Editor, is TabData.Custom -> tabData.closable && (tabState.isHovered || tabState.isSelected)
}

if (showCloseIcon) {
val closeActionInteractionSource = remember { MutableInteractionSource() }
LaunchedEffect(closeActionInteractionSource) {
Expand Down Expand Up @@ -166,7 +176,7 @@ internal fun TabImpl(
)
.size(16.dp),
painter = closePainter,
contentDescription = "Close tab ${tabData.label}",
contentDescription = "Close tab",
)
} else if (tabData.closable) {
Spacer(Modifier.size(16.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,8 @@ public val LocalEditorTabStyle: ProvidableCompositionLocal<TabStyle> =
staticCompositionLocalOf {
error("No LocalTabStyle provided. Have you forgotten the theme?")
}

public val LocalCustomTabStyle: ProvidableCompositionLocal<TabStyle> =
staticCompositionLocalOf {
error("No LocalTabStyle provided. Have you forgotten the theme?")
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.jetbrains.jewel.ui.component.styling.LinkStyle
import org.jetbrains.jewel.ui.component.styling.LocalCheckboxStyle
import org.jetbrains.jewel.ui.component.styling.LocalChipStyle
import org.jetbrains.jewel.ui.component.styling.LocalCircularProgressStyle
import org.jetbrains.jewel.ui.component.styling.LocalCustomTabStyle
import org.jetbrains.jewel.ui.component.styling.LocalDefaultButtonStyle
import org.jetbrains.jewel.ui.component.styling.LocalDefaultDropdownStyle
import org.jetbrains.jewel.ui.component.styling.LocalDefaultTabStyle
Expand Down Expand Up @@ -142,7 +143,7 @@ public val JewelTheme.Companion.treeStyle: LazyTreeStyle
@ReadOnlyComposable
get() = LocalLazyTreeStyle.current

public val JewelTheme.Companion.defaultTabStyle: TabStyle
public val JewelTheme.Companion.toolWindowTabStyle: TabStyle
@Composable
@ReadOnlyComposable
get() = LocalDefaultTabStyle.current
Expand All @@ -152,6 +153,11 @@ public val JewelTheme.Companion.editorTabStyle: TabStyle
@ReadOnlyComposable
get() = LocalEditorTabStyle.current

public val JewelTheme.Companion.customTabStyle: TabStyle
@Composable
@ReadOnlyComposable
get() = LocalCustomTabStyle.current

public val JewelTheme.Companion.circularProgressStyle: CircularProgressStyle
@Composable
@ReadOnlyComposable
Expand Down

0 comments on commit 2077d68

Please sign in to comment.