Skip to content

Commit 200fcc9

Browse files
authored
Merge pull request #5 from kdroidFilter/feat/scaffold-push-content-responsive
feat(scaffold): add pushContent mode and responsive sample layout
2 parents 5e6b608 + 7283633 commit 200fcc9

10 files changed

Lines changed: 467 additions & 313 deletions

File tree

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components/Scaffold.kt

Lines changed: 107 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package io.github.kdroidfilter.nucleus.ui.apple.macos.components
22

33
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.EnterTransition
5+
import androidx.compose.animation.ExitTransition
46
import androidx.compose.animation.expandHorizontally
57
import androidx.compose.animation.fadeIn
68
import androidx.compose.animation.fadeOut
79
import androidx.compose.animation.shrinkHorizontally
810
import androidx.compose.animation.slideInHorizontally
911
import androidx.compose.animation.slideOutHorizontally
12+
import androidx.compose.animation.core.animateDpAsState
1013
import androidx.compose.foundation.background
1114
import androidx.compose.foundation.layout.Box
15+
import androidx.compose.foundation.layout.BoxWithConstraints
1216
import androidx.compose.foundation.layout.PaddingValues
1317
import androidx.compose.foundation.layout.Row
1418
import androidx.compose.foundation.layout.fillMaxHeight
@@ -17,6 +21,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
1721
import androidx.compose.foundation.layout.height
1822
import androidx.compose.foundation.layout.padding
1923
import androidx.compose.foundation.layout.width
24+
import androidx.compose.ui.draw.clipToBounds
2025
import androidx.compose.runtime.Composable
2126
import androidx.compose.runtime.CompositionLocalProvider
2227
import 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

Comments
 (0)