11package io.github.kdroidfilter.nucleus.ui.apple.macos.components
22
33import androidx.compose.animation.AnimatedVisibility
4+ import androidx.compose.animation.EnterTransition
5+ import androidx.compose.animation.ExitTransition
46import androidx.compose.animation.expandHorizontally
57import androidx.compose.animation.fadeIn
68import androidx.compose.animation.fadeOut
79import androidx.compose.animation.shrinkHorizontally
810import androidx.compose.animation.slideInHorizontally
911import androidx.compose.animation.slideOutHorizontally
12+ import androidx.compose.animation.core.animateDpAsState
1013import androidx.compose.foundation.background
1114import androidx.compose.foundation.layout.Box
15+ import androidx.compose.foundation.layout.BoxWithConstraints
1216import androidx.compose.foundation.layout.PaddingValues
1317import androidx.compose.foundation.layout.Row
1418import androidx.compose.foundation.layout.fillMaxHeight
@@ -17,6 +21,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
1721import androidx.compose.foundation.layout.height
1822import androidx.compose.foundation.layout.padding
1923import androidx.compose.foundation.layout.width
24+ import androidx.compose.ui.draw.clipToBounds
2025import androidx.compose.runtime.Composable
2126import androidx.compose.runtime.CompositionLocalProvider
2227import androidx.compose.runtime.getValue
@@ -107,6 +112,7 @@ fun Scaffold(
107112 showDividers : Boolean = false,
108113 bottomBar : (@Composable () -> Unit )? = null,
109114 bottomBarHeight : Int = 38,
115+ pushContent : Boolean = false,
110116 content : @Composable (PaddingValues ) -> Unit ,
111117) {
112118 // --- Resolve effective column visibility ---
@@ -131,9 +137,9 @@ fun Scaffold(
131137 }
132138
133139 // --- Column width states ---
134- var currentSidebarWidth by remember { mutableStateOf(sidebarWidth.idealOrFixed()) }
135- var currentContentListWidth by remember { mutableStateOf(contentListWidth.idealOrFixed()) }
136- var currentInspectorWidth by remember { mutableStateOf(inspectorWidth.idealOrFixed()) }
140+ var currentSidebarWidth by remember(sidebarWidth) { mutableStateOf(sidebarWidth.idealOrFixed()) }
141+ var currentContentListWidth by remember(contentListWidth) { mutableStateOf(contentListWidth.idealOrFixed()) }
142+ var currentInspectorWidth by remember(inspectorWidth) { mutableStateOf(inspectorWidth.idealOrFixed()) }
137143
138144 // --- Content padding ---
139145 val topPadding = if (titleBar != null ) titleBarHeight.dp else 0 .dp
@@ -161,62 +167,59 @@ fun Scaffold(
161167 }
162168
163169 CompositionLocalProvider (LocalTitleBarHeight provides topPadding) {
164- Row (
165- modifier = modifier
166- .fillMaxSize()
167- .background(containerColor),
168- ) {
169- // ---- Sidebar (full height, side-by-side with the title bar) ----
170- if (sidebar != null ) {
171- val sidebarSizeTween = tween<IntSize >(durationMillis = 250 , easing = FastOutSlowInEasing )
172- val sidebarOffsetTween = tween<IntOffset >(durationMillis = 250 , easing = FastOutSlowInEasing )
173- AnimatedVisibility (
174- visible = showSidebar,
175- enter = slideInHorizontally(
176- animationSpec = sidebarOffsetTween,
177- initialOffsetX = { - it },
178- ) + expandHorizontally(
179- animationSpec = sidebarSizeTween,
180- expandFrom = Alignment .Start ,
181- ),
182- exit = slideOutHorizontally(
183- animationSpec = sidebarOffsetTween,
184- targetOffsetX = { - it },
185- ) + shrinkHorizontally(
186- animationSpec = sidebarSizeTween,
187- shrinkTowards = Alignment .Start ,
188- ),
189- ) {
190- val sidebarResizeCallbacks = if (sidebarWidth is ColumnWidth .Flexible ) {
191- val flex = sidebarWidth
192- SidebarResizeCallbacks (
193- onDrag = { delta ->
194- currentSidebarWidth = (currentSidebarWidth + delta)
195- .coerceIn(flex.min, flex.max)
196- },
197- onReset = { currentSidebarWidth = flex.ideal },
198- )
199- } else {
200- null
201- }
202170
203- val hideCallback: (() -> Unit )? = if (managedToggle) toggleSidebar else null
204- CompositionLocalProvider (
205- LocalSidebarWidth provides currentSidebarWidth,
206- LocalSidebarResize provides sidebarResizeCallbacks,
207- LocalSidebarHide provides hideCallback,
208- LocalSidebarVisible provides showSidebar,
171+ @Composable
172+ fun SidebarBlock (slideOnly : Boolean ) {
173+ if (sidebar != null ) {
174+ val sidebarSizeTween = tween<IntSize >(durationMillis = 250 , easing = FastOutSlowInEasing )
175+ val sidebarOffsetTween = tween<IntOffset >(durationMillis = 250 , easing = FastOutSlowInEasing )
176+ AnimatedVisibility (
177+ visible = showSidebar,
178+ enter = slideInHorizontally(
179+ animationSpec = sidebarOffsetTween,
180+ initialOffsetX = { - it },
181+ ) + if (! slideOnly) expandHorizontally(
182+ animationSpec = sidebarSizeTween,
183+ expandFrom = Alignment .Start ,
184+ ) else EnterTransition .None ,
185+ exit = slideOutHorizontally(
186+ animationSpec = sidebarOffsetTween,
187+ targetOffsetX = { - it },
188+ ) + if (! slideOnly) shrinkHorizontally(
189+ animationSpec = sidebarSizeTween,
190+ shrinkTowards = Alignment .Start ,
191+ ) else ExitTransition .None ,
209192 ) {
210- sidebar()
193+ val sidebarResizeCallbacks = if (sidebarWidth is ColumnWidth .Flexible ) {
194+ val flex = sidebarWidth
195+ SidebarResizeCallbacks (
196+ onDrag = { delta ->
197+ currentSidebarWidth = (currentSidebarWidth + delta)
198+ .coerceIn(flex.min, flex.max)
199+ },
200+ onReset = { currentSidebarWidth = flex.ideal },
201+ )
202+ } else {
203+ null
204+ }
205+
206+ val hideCallback: (() -> Unit )? = if (managedToggle) toggleSidebar else null
207+ CompositionLocalProvider (
208+ LocalSidebarWidth provides currentSidebarWidth,
209+ LocalSidebarResize provides sidebarResizeCallbacks,
210+ LocalSidebarHide provides hideCallback,
211+ LocalSidebarVisible provides showSidebar,
212+ ) {
213+ sidebar()
214+ }
211215 }
212216 }
213217 }
214218
215- // ---- Content area (title bar + columns) ----
219+ @Composable
220+ fun ContentArea (contentModifier : Modifier ) {
216221 Box (
217- modifier = Modifier
218- .weight(1f )
219- .fillMaxHeight(),
222+ modifier = contentModifier,
220223 ) {
221224 // Content layer captured for title bar glass
222225 Box (
@@ -413,6 +416,57 @@ fun Scaffold(
413416 }
414417 }
415418 }
416- }
419+ }
420+
421+ if (pushContent && sidebar != null ) {
422+ BoxWithConstraints (
423+ modifier = modifier.fillMaxSize().background(containerColor).clipToBounds(),
424+ ) {
425+ val parentWidth = maxWidth
426+ val sidebarOffset by animateDpAsState(
427+ targetValue = if (showSidebar) 0 .dp else - currentSidebarWidth,
428+ animationSpec = tween(durationMillis = 250 , easing = FastOutSlowInEasing ),
429+ )
430+ // Content: full width, pushed right when sidebar is shown
431+ ContentArea (
432+ Modifier
433+ .width(parentWidth)
434+ .fillMaxHeight()
435+ .offset { IntOffset ((sidebarOffset + currentSidebarWidth).roundToPx(), 0 ) },
436+ )
437+ // Sidebar: slides in from the left, driven by same sidebarOffset
438+ val sidebarResizeCallbacks = if (sidebarWidth is ColumnWidth .Flexible ) {
439+ SidebarResizeCallbacks (
440+ onDrag = { delta ->
441+ currentSidebarWidth = (currentSidebarWidth + delta)
442+ .coerceIn(sidebarWidth.min, sidebarWidth.max)
443+ },
444+ onReset = { currentSidebarWidth = sidebarWidth.ideal },
445+ )
446+ } else {
447+ null
448+ }
449+ Box (
450+ modifier = Modifier
451+ .width(currentSidebarWidth)
452+ .fillMaxHeight()
453+ .offset { IntOffset (sidebarOffset.roundToPx(), 0 ) },
454+ ) {
455+ CompositionLocalProvider (
456+ LocalSidebarWidth provides currentSidebarWidth,
457+ LocalSidebarResize provides sidebarResizeCallbacks,
458+ LocalSidebarHide provides if (managedToggle) toggleSidebar else null ,
459+ LocalSidebarVisible provides showSidebar,
460+ ) {
461+ sidebar()
462+ }
463+ }
464+ }
465+ } else {
466+ Row (modifier = modifier.fillMaxSize().background(containerColor)) {
467+ SidebarBlock (slideOnly = false )
468+ ContentArea (Modifier .weight(1f ).fillMaxHeight())
469+ }
470+ }
417471 }
418472}
0 commit comments