@@ -7,8 +7,6 @@ import androidx.compose.animation.expandHorizontally
77import androidx.compose.animation.fadeIn
88import androidx.compose.animation.fadeOut
99import androidx.compose.animation.shrinkHorizontally
10- import androidx.compose.animation.slideInHorizontally
11- import androidx.compose.animation.slideOutHorizontally
1210import androidx.compose.animation.core.animateDpAsState
1311import androidx.compose.foundation.background
1412import androidx.compose.foundation.layout.Box
@@ -22,6 +20,7 @@ import androidx.compose.foundation.layout.height
2220import androidx.compose.foundation.layout.padding
2321import androidx.compose.foundation.layout.width
2422import androidx.compose.ui.draw.clipToBounds
23+ import androidx.compose.ui.draw.drawWithContent
2524import androidx.compose.runtime.Composable
2625import androidx.compose.runtime.CompositionLocalProvider
2726import androidx.compose.runtime.getValue
@@ -40,7 +39,6 @@ import io.github.fletchmckee.liquid.liquefiable
4039import io.github.fletchmckee.liquid.liquid
4140import io.github.fletchmckee.liquid.rememberLiquidState
4241import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.liquidGlassFade
43- import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.MacosDuration
4442import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SpringPreset
4543import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.MacosTheme
4644import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalLiquidState
@@ -52,13 +50,17 @@ import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalToolbarGlassStat
5250import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarHide
5351import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarVisible
5452import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.macosSpring
55- import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.macosTween
5653import androidx.compose.animation.core.FastOutSlowInEasing
5754import androidx.compose.animation.core.tween
55+ import androidx.compose.animation.slideInHorizontally
56+ import androidx.compose.animation.slideOutHorizontally
5857import androidx.compose.foundation.layout.offset
5958import androidx.compose.runtime.LaunchedEffect
59+ import androidx.compose.foundation.clickable
60+ import androidx.compose.foundation.interaction.MutableInteractionSource
6061import androidx.compose.ui.unit.IntOffset
6162import androidx.compose.ui.unit.IntSize
63+ import androidx.compose.ui.zIndex
6264
6365/* *
6466 * macOS-style scaffold layout with optional sidebar, content list, inspector,
@@ -89,6 +91,8 @@ import androidx.compose.ui.unit.IntSize
8991 * @param titleBarHeight Height of the title bar for content inset.
9092 * @param bottomBar Optional bottom bar composable overlaying the content area.
9193 * @param bottomBarHeight Height of the bottom bar.
94+ * @param dismissPanelsOnContentTap When true (and [pushContent] is enabled), tapping the content
95+ * area dismisses any open sidebar or inspector panel.
9296 * @param content Main content area. Receives [PaddingValues] for title bar and bottom bar insets.
9397 */
9498@Composable
@@ -113,6 +117,7 @@ fun Scaffold(
113117 bottomBar : (@Composable () -> Unit )? = null,
114118 bottomBarHeight : Int = 38,
115119 pushContent : Boolean = false,
120+ dismissPanelsOnContentTap : Boolean = false,
116121 content : @Composable (PaddingValues ) -> Unit ,
117122) {
118123 // --- Resolve effective column visibility ---
@@ -218,8 +223,29 @@ fun Scaffold(
218223
219224 @Composable
220225 fun ContentArea (contentModifier : Modifier ) {
226+ val showInlineInspector = ! pushContent && inspector != null
227+ val animatedInspectorWidth by animateDpAsState(
228+ targetValue = if (showInlineInspector && inspectorVisible) currentInspectorWidth else 0 .dp,
229+ animationSpec = tween(durationMillis = 250 , easing = FastOutSlowInEasing ),
230+ )
231+ val separatorColor = if (isDark) Color .White .copy(alpha = 0.10f ) else Color .Black .copy(alpha = 0.10f )
232+
221233 Box (
222- modifier = contentModifier,
234+ modifier = contentModifier
235+ .clipToBounds()
236+ .drawWithContent {
237+ drawContent()
238+ // Inspector separator drawn on top of everything (including title bar glass)
239+ if (animatedInspectorWidth > 0 .dp) {
240+ val x = size.width - animatedInspectorWidth.toPx()
241+ drawLine(
242+ color = separatorColor,
243+ start = Offset (x, 0f ),
244+ end = Offset (x, size.height),
245+ strokeWidth = 1 .dp.toPx(),
246+ )
247+ }
248+ },
223249 ) {
224250 // Content layer captured for title bar glass
225251 Box (
@@ -323,56 +349,50 @@ fun Scaffold(
323349 }
324350 }
325351
326- // ---- Inspector (right panel) ----
327- if (inspector != null ) {
352+ // ---- Inspector (resize mode, non-push only) ----
353+ if (showInlineInspector) {
354+ val inspectorSizeTween = tween<IntSize >(durationMillis = 250 , easing = FastOutSlowInEasing )
328355 AnimatedVisibility (
329356 visible = inspectorVisible,
330357 enter = expandHorizontally(
331- animationSpec = macosSpring( SpringPreset . Snappy ) ,
358+ animationSpec = inspectorSizeTween ,
332359 expandFrom = Alignment .End ,
333360 ),
334361 exit = shrinkHorizontally(
335- animationSpec = macosSpring( SpringPreset . Snappy ) ,
362+ animationSpec = inspectorSizeTween ,
336363 shrinkTowards = Alignment .End ,
337364 ),
338365 ) {
339- Row {
340- // Inspector divider
341- if (inspectorWidth is ColumnWidth .Flexible ) {
342- val flex = inspectorWidth
343- ColumnDivider (
344- onDrag = { delta ->
345- currentInspectorWidth = (currentInspectorWidth - delta)
346- .coerceIn(flex.min, flex.max)
347- },
348- onReset = { currentInspectorWidth = flex.ideal },
349- )
350- }
351-
352- Box (
353- modifier = Modifier
354- .width(currentInspectorWidth)
355- .fillMaxHeight()
356- .padding(top = topPadding)
357- .background(MacosTheme .colorScheme.surface)
358- .drawBehind {
359- // Left border
360- drawLine(
361- color = borderColor,
362- start = Offset (0f , 0f ),
363- end = Offset (0f , size.height),
364- strokeWidth = 1 .dp.toPx(),
365- )
366- },
367- ) {
368- inspector()
369- }
366+ Box (
367+ modifier = Modifier
368+ .width(currentInspectorWidth)
369+ .fillMaxHeight(),
370+ ) {
371+ inspector()
370372 }
371373 }
372374 }
373375 }
374376 }
375377
378+ // ---- Inspector separator (non-push only, outside liquefiable) ----
379+ if (showInlineInspector) {
380+ if (inspectorWidth is ColumnWidth .Flexible ) {
381+ val flex = inspectorWidth
382+ ColumnDivider (
383+ modifier = Modifier
384+ .align(Alignment .TopEnd )
385+ .fillMaxHeight()
386+ .offset { IntOffset (- animatedInspectorWidth.roundToPx(), 0 ) },
387+ onDrag = { delta ->
388+ currentInspectorWidth = (currentInspectorWidth - delta)
389+ .coerceIn(flex.min, flex.max)
390+ },
391+ onReset = { currentInspectorWidth = flex.ideal },
392+ )
393+ }
394+ }
395+
376396 // ---- Title bar overlay (over content area only) ----
377397 if (titleBar != null || managedToggle) {
378398 val titleBarGlassModifier = Modifier .liquidGlassFade(
@@ -415,10 +435,12 @@ fun Scaffold(
415435 }
416436 }
417437 }
438+
418439 }
419440 }
420441
421442 if (pushContent && sidebar != null ) {
443+ val pushSeparatorColor = if (isDark) Color .White .copy(alpha = 0.10f ) else Color .Black .copy(alpha = 0.10f )
422444 BoxWithConstraints (
423445 modifier = modifier.fillMaxSize().background(containerColor).clipToBounds(),
424446 ) {
@@ -427,13 +449,38 @@ fun Scaffold(
427449 targetValue = if (showSidebar) 0 .dp else - currentSidebarWidth,
428450 animationSpec = tween(durationMillis = 250 , easing = FastOutSlowInEasing ),
429451 )
430- // Content: full width, pushed right when sidebar is shown
431- ContentArea (
432- Modifier
452+ val inspectorOffset by animateDpAsState(
453+ targetValue = if (inspectorVisible && inspector != null ) 0 .dp else currentInspectorWidth,
454+ animationSpec = tween(durationMillis = 250 , easing = FastOutSlowInEasing ),
455+ )
456+ // Content: full width, pushed right by sidebar and left by inspector
457+ Box (
458+ modifier = Modifier
433459 .width(parentWidth)
434460 .fillMaxHeight()
435- .offset { IntOffset ((sidebarOffset + currentSidebarWidth).roundToPx(), 0 ) },
436- )
461+ .offset { IntOffset (
462+ ((sidebarOffset + currentSidebarWidth) - (currentInspectorWidth - inspectorOffset)).roundToPx(),
463+ 0 ,
464+ ) },
465+ ) {
466+ ContentArea (Modifier .fillMaxSize())
467+
468+ // Dismiss overlay: tap content to close sidebar / inspector
469+ if (dismissPanelsOnContentTap && (showSidebar || (inspectorVisible && inspector != null ))) {
470+ Box (
471+ modifier = Modifier
472+ .fillMaxSize()
473+ .zIndex(10f )
474+ .clickable(
475+ interactionSource = remember { MutableInteractionSource () },
476+ indication = null ,
477+ ) {
478+ if (showSidebar && managedToggle) toggleSidebar()
479+ if (inspectorVisible) onInspectorVisibleChange?.invoke(false )
480+ },
481+ )
482+ }
483+ }
437484 // Sidebar: slides in from the left, driven by same sidebarOffset
438485 val sidebarResizeCallbacks = if (sidebarWidth is ColumnWidth .Flexible ) {
439486 SidebarResizeCallbacks (
@@ -461,6 +508,27 @@ fun Scaffold(
461508 sidebar()
462509 }
463510 }
511+ // Inspector: slides in from the right, pushing content left
512+ if (inspector != null ) {
513+ Box (
514+ modifier = Modifier
515+ .width(currentInspectorWidth)
516+ .fillMaxHeight()
517+ .align(Alignment .TopEnd )
518+ .offset { IntOffset (inspectorOffset.roundToPx(), 0 ) }
519+ .drawWithContent {
520+ drawContent()
521+ drawLine(
522+ color = pushSeparatorColor,
523+ start = Offset (0f , 0f ),
524+ end = Offset (0f , size.height),
525+ strokeWidth = 1 .dp.toPx(),
526+ )
527+ },
528+ ) {
529+ inspector()
530+ }
531+ }
464532 }
465533 } else {
466534 Row (modifier = modifier.fillMaxSize().background(containerColor)) {
0 commit comments