@@ -20,13 +20,17 @@ package com.vaticle.typedb.studio.view.material
2020
2121import androidx.compose.foundation.ScrollState
2222import androidx.compose.foundation.background
23+ import androidx.compose.foundation.clickable
2324import androidx.compose.foundation.horizontalScroll
25+ import androidx.compose.foundation.layout.Arrangement
2426import androidx.compose.foundation.layout.Box
2527import androidx.compose.foundation.layout.Column
2628import androidx.compose.foundation.layout.Row
2729import androidx.compose.foundation.layout.Spacer
2830import androidx.compose.foundation.layout.fillMaxWidth
2931import androidx.compose.foundation.layout.height
32+ import androidx.compose.foundation.layout.offset
33+ import androidx.compose.foundation.layout.requiredWidth
3034import androidx.compose.foundation.layout.size
3135import androidx.compose.foundation.layout.width
3236import androidx.compose.foundation.layout.widthIn
@@ -39,6 +43,7 @@ import androidx.compose.runtime.setValue
3943import androidx.compose.ui.Alignment
4044import androidx.compose.ui.ExperimentalComposeUiApi
4145import androidx.compose.ui.Modifier
46+ import androidx.compose.ui.draw.rotate
4247import androidx.compose.ui.graphics.Color
4348import androidx.compose.ui.input.pointer.PointerIconDefaults
4449import androidx.compose.ui.input.pointer.PointerInputScope
@@ -52,8 +57,6 @@ import androidx.compose.ui.unit.dp
5257import androidx.compose.ui.unit.sp
5358import com.vaticle.typedb.studio.view.common.Util.toDP
5459import com.vaticle.typedb.studio.view.common.theme.Theme
55- import com.vaticle.typedb.studio.view.common.theme.Theme.PANEL_BAR_HEIGHT
56- import com.vaticle.typedb.studio.view.common.theme.Theme.PANEL_BAR_SPACING
5760import com.vaticle.typedb.studio.view.material.Form.IconArg
5861import com.vaticle.typedb.studio.view.material.Form.IconButtonArg
5962import java.awt.event.MouseEvent
@@ -64,10 +67,73 @@ object Tabs {
6467
6568 private val TAB_UNDERLINE_HEIGHT = 2 .dp
6669 private val TAB_SCROLL_DELTA = 200 .dp
67- private val ICON_SIZE = 10 .sp
70+ private val TAB_ICON_SIZE = 10 .sp
71+ private val TAB_SPACING = Theme .PANEL_BAR_SPACING
72+
73+ object Vertical {
74+
75+ val WIDTH = 22 .dp
76+ private val TAB_HEIGHT = 100 .dp
77+ private val TAB_OFFSET = (- 40 ).dp
78+
79+ enum class Position (internal val degree : Float ) { LEFT (- 90f ), RIGHT (90f ) }
80+
81+ @Composable
82+ fun <T : Any > Layout (
83+ tabs : List <T >,
84+ position : Position ,
85+ labelFn : (T ) -> String ,
86+ iconFn : (T ) -> Icon .Code ,
87+ isActiveFn : (T ) -> Boolean ,
88+ onClick : (T ) -> Unit ,
89+ ) {
90+ Column (
91+ modifier = Modifier .width(WIDTH ).background(Theme .studio.backgroundMedium),
92+ verticalArrangement = Arrangement .Top
93+ ) { tabs.forEach { Tab (it, position, labelFn, iconFn, isActiveFn, onClick) } }
94+ }
95+
96+ @OptIn(ExperimentalComposeUiApi ::class )
97+ @Composable
98+ private fun <T : Any > Tab (
99+ tab : T ,
100+ position : Position ,
101+ labelFn : (T ) -> String ,
102+ iconFn : (T ) -> Icon .Code ,
103+ isActiveFn : (T ) -> Boolean ,
104+ onClick : (T ) -> Unit ,
105+ ) {
106+ @Composable
107+ fun bgColor (): Color = if (isActiveFn(tab)) Theme .studio.surface else Theme .studio.backgroundDark
108+ Box (
109+ modifier = Modifier
110+ .fillMaxWidth()
111+ .height(TAB_HEIGHT )
112+ .pointerHoverIcon(PointerIconDefaults .Hand )
113+ .clickable { onClick(tab) }
114+ ) {
115+ Row (
116+ verticalAlignment = Alignment .CenterVertically ,
117+ modifier = Modifier .requiredWidth(TAB_HEIGHT )
118+ .rotate(position.degree)
119+ .offset(x = TAB_OFFSET )
120+ .background(color = bgColor())
121+ ) {
122+ Spacer (modifier = Modifier .weight(1f ))
123+ Icon .Render (icon = iconFn(tab), size = TAB_ICON_SIZE )
124+ Spacer (modifier = Modifier .width(TAB_SPACING ))
125+ Form .Text (value = labelFn(tab))
126+ Spacer (modifier = Modifier .weight(1f ))
127+ }
128+ }
129+ Separator .Horizontal ()
130+ }
131+ }
68132
69133 object Horizontal {
70134
135+ private val HEIGHT = Theme .PANEL_BAR_HEIGHT
136+
71137 enum class Position { TOP , BOTTOM }
72138
73139 class State <T : Any > constructor(private val coroutineScope : CoroutineScope ) {
@@ -118,15 +184,15 @@ object Tabs {
118184 state.density = LocalDensity .current.density
119185 val closedTabs = state.openedTabSize.keys - tabs.toSet()
120186 closedTabs.forEach { state.openedTabSize.remove(it) }
121- Row (Modifier .fillMaxWidth().height(PANEL_BAR_HEIGHT ).onSizeChanged {
122- state.maxWidth = toDP(it.width, state.density) - PANEL_BAR_HEIGHT * 3
187+ Row (Modifier .fillMaxWidth().height(HEIGHT ).onSizeChanged {
188+ state.maxWidth = toDP(it.width, state.density) - HEIGHT * 3
123189 }) {
124190 if (tabs.isNotEmpty()) Separator .Vertical ()
125191 if (state.scroller.maxValue > 0 ) {
126192 PreviousTabsButton (state)
127193 Separator .Vertical ()
128194 }
129- Row (Modifier .widthIn(max = state.maxWidth).height(PANEL_BAR_HEIGHT ).horizontalScroll(state.scroller)) {
195+ Row (Modifier .widthIn(max = state.maxWidth).height(HEIGHT ).horizontalScroll(state.scroller)) {
130196 tabs.forEach { tab ->
131197 val icon = iconFn?.let { it(tab) }
132198 val label = labelFn(tab)
@@ -157,7 +223,7 @@ object Tabs {
157223
158224 @Composable
159225 private fun Spacer () {
160- Spacer (modifier = Modifier .width(PANEL_BAR_SPACING ))
226+ Spacer (modifier = Modifier .width(TAB_SPACING ))
161227 }
162228
163229 @OptIn(ExperimentalComposeUiApi ::class )
@@ -170,7 +236,7 @@ object Tabs {
170236 ) {
171237 val contextMenuState = remember { ContextMenu .State () }
172238 val bgColor = if (isActive) Theme .studio.primary else Color .Transparent
173- val height = if (isActive) PANEL_BAR_HEIGHT - TAB_UNDERLINE_HEIGHT else PANEL_BAR_HEIGHT
239+ val height = if (isActive) HEIGHT - TAB_UNDERLINE_HEIGHT else HEIGHT
174240 var width by remember { mutableStateOf(0 .dp) }
175241
176242 Box {
@@ -188,7 +254,7 @@ object Tabs {
188254 trailingButton?.let { Button (it) }
189255 icon?.let {
190256 Spacer ()
191- Icon .Render (icon = it.code, color = it.color(), size = ICON_SIZE )
257+ Icon .Render (icon = it.code, color = it.color(), size = TAB_ICON_SIZE )
192258 Spacer ()
193259 }
194260 if (trailingButton == null && icon == null ) Spacer ()
@@ -220,7 +286,7 @@ object Tabs {
220286 private fun <T : Any > PreviousTabsButton (state : State <T >) {
221287 Form .IconButton (
222288 icon = Icon .Code .CARET_LEFT ,
223- modifier = Modifier .size(PANEL_BAR_HEIGHT ),
289+ modifier = Modifier .size(HEIGHT ),
224290 bgColor = Color .Transparent ,
225291 roundedCorners = Theme .RoundedCorners .NONE ,
226292 enabled = state.scroller.value > 0
@@ -231,7 +297,7 @@ object Tabs {
231297 private fun <T : Any > NextTabsButton (state : State <T >) {
232298 Form .IconButton (
233299 icon = Icon .Code .CARET_RIGHT ,
234- modifier = Modifier .size(PANEL_BAR_HEIGHT ),
300+ modifier = Modifier .size(HEIGHT ),
235301 bgColor = Color .Transparent ,
236302 roundedCorners = Theme .RoundedCorners .NONE ,
237303 enabled = state.scroller.value < state.scroller.maxValue
@@ -243,7 +309,7 @@ object Tabs {
243309 Form .IconButton (
244310 icon = buttonArg.icon,
245311 hoverIcon = buttonArg.hoverIcon,
246- modifier = Modifier .size(PANEL_BAR_HEIGHT ),
312+ modifier = Modifier .size(HEIGHT ),
247313 iconColor = buttonArg.color(),
248314 iconHoverColor = buttonArg.hoverColor?.invoke(),
249315 disabledColor = buttonArg.disabledColor?.invoke(),
0 commit comments