@@ -14,6 +14,7 @@ import androidx.compose.runtime.*
1414import androidx.compose.ui.Alignment
1515import androidx.compose.ui.Modifier
1616import androidx.compose.ui.draw.clip
17+ import androidx.compose.ui.draw.clipToBounds
1718import androidx.compose.ui.draw.drawBehind
1819import androidx.compose.ui.geometry.Offset
1920import androidx.compose.ui.graphics.Color
@@ -111,6 +112,7 @@ fun TitleBar(
111112 glass : Boolean = false,
112113 height : Int = style.height,
113114 navigationActionsMinWidth : Dp = style.navMinWidth,
115+ pushActionsOnExpand : Boolean = false,
114116) {
115117 val content = @Composable {
116118 TitleBarContent (
@@ -126,6 +128,7 @@ fun TitleBar(
126128 glass = glass,
127129 height = height,
128130 navigationActionsMinWidth = navigationActionsMinWidth,
131+ pushActionsOnExpand = pushActionsOnExpand,
129132 )
130133 }
131134
@@ -152,6 +155,7 @@ private fun TitleBarContent(
152155 glass : Boolean ,
153156 height : Int ,
154157 navigationActionsMinWidth : Dp = style.navMinWidth,
158+ pushActionsOnExpand : Boolean = false,
155159) {
156160 val isDark = MacosTheme .colorScheme.isDark
157161 val borderColor = if (isDark) Color .Black .copy(alpha = 0.5f ) else Color .Black .copy(alpha = 0.1f )
@@ -176,83 +180,131 @@ private fun TitleBarContent(
176180
177181 val onTitleBarDoubleClick = LocalTitleBarDoubleClick .current
178182
183+ val outerModifier = modifier
184+ .fillMaxWidth()
185+ .height(height.dp)
186+ .then(
187+ if (onTitleBarDoubleClick != null ) {
188+ Modifier .pointerInput(onTitleBarDoubleClick) {
189+ awaitPointerEventScope {
190+ var lastPressTime = 0L
191+ while (true ) {
192+ val event = awaitPointerEvent(PointerEventPass .Final )
193+ if (event.type == PointerEventType .Press &&
194+ event.changes.none { it.isConsumed }
195+ ) {
196+ val now = event.changes.first().uptimeMillis
197+ if (now - lastPressTime < viewConfiguration.doubleTapTimeoutMillis) {
198+ onTitleBarDoubleClick()
199+ lastPressTime = 0L
200+ } else {
201+ lastPressTime = now
202+ }
203+ }
204+ }
205+ }
206+ }
207+ } else {
208+ Modifier
209+ },
210+ )
211+ .then(bgModifier)
212+ .then(
213+ if (showBorder) {
214+ Modifier .drawBehind {
215+ drawLine(
216+ color = borderColor,
217+ start = Offset (0f , size.height),
218+ end = Offset (size.width, size.height),
219+ strokeWidth = 0.5 .dp.toPx(),
220+ )
221+ }
222+ } else {
223+ Modifier
224+ },
225+ )
226+
179227 CompositionLocalProvider (
180228 LocalTitleBarStyle provides style,
181229 LocalTextStyle provides titleTextStyle,
182230 ) {
183- Row (
184- modifier = modifier
185- .fillMaxWidth()
186- .height(height.dp)
187- .then(
188- if (onTitleBarDoubleClick != null ) {
189- Modifier .pointerInput(onTitleBarDoubleClick) {
190- awaitPointerEventScope {
191- var lastPressTime = 0L
192- while (true ) {
193- val event = awaitPointerEvent(PointerEventPass .Final )
194- if (event.type == PointerEventType .Press &&
195- event.changes.none { it.isConsumed }
196- ) {
197- val now = event.changes.first().uptimeMillis
198- if (now - lastPressTime < viewConfiguration.doubleTapTimeoutMillis) {
199- onTitleBarDoubleClick()
200- lastPressTime = 0L
201- } else {
202- lastPressTime = now
203- }
204- }
205- }
206- }
207- }
208- } else {
209- Modifier
210- },
211- )
212- .then(bgModifier)
213- .then(
214- if (showBorder) {
215- Modifier .drawBehind {
216- drawLine(
217- color = borderColor,
218- start = Offset (0f , size.height),
219- end = Offset (size.width, size.height),
220- strokeWidth = 0.5 .dp.toPx(),
221- )
222- }
223- } else {
224- Modifier
225- },
226- )
227- .padding(horizontal = style.horizontalPadding),
228- verticalAlignment = Alignment .CenterVertically ,
229- ) {
230- // Left section: Nav Actions
231- Row (
232- verticalAlignment = Alignment .CenterVertically ,
233- modifier = Modifier .padding(end = 8 .dp).widthIn(min = navigationActionsMinWidth),
234- ) {
235- navigationActions()
236- }
231+ if (pushActionsOnExpand) {
232+ val density = LocalDensity .current
233+ var actionsWidthPx by remember { mutableStateOf(0 ) }
234+ val actionsMinWidthPx = remember(style, density) { with (density) { style.actionsMinWidth.roundToPx() } }
235+ val pushOffsetPx = (actionsWidthPx - actionsMinWidthPx).coerceAtLeast(0 )
236+
237+ Box (modifier = outerModifier.clipToBounds().padding(horizontal = style.horizontalPadding)) {
238+ // Left section (nav + title): fills full width, pushed left as actions expand
239+ Row (
240+ modifier = Modifier
241+ .fillMaxWidth()
242+ .fillMaxHeight()
243+ .offset { IntOffset (- pushOffsetPx, 0 ) },
244+ verticalAlignment = Alignment .CenterVertically ,
245+ ) {
246+ Row (
247+ verticalAlignment = Alignment .CenterVertically ,
248+ modifier = Modifier .padding(end = 8 .dp).widthIn(min = navigationActionsMinWidth),
249+ ) {
250+ navigationActions()
251+ }
252+ Box (
253+ modifier = Modifier
254+ .weight(1f )
255+ .fillMaxHeight()
256+ .graphicsLayer { alpha = if (showsTitle) 1f else 0f },
257+ contentAlignment = Alignment .Center ,
258+ ) {
259+ title()
260+ }
261+ }
237262
238- // Center section: Title or Search
239- Box (
240- modifier = Modifier
241- .weight(1f )
242- .fillMaxHeight()
243- .graphicsLayer { alpha = if (showsTitle) 1f else 0f },
244- contentAlignment = Alignment .Center ,
245- ) {
246- title()
263+ // Right section (actions): anchored at end, measured to drive push offset
264+ Row (
265+ verticalAlignment = Alignment .CenterVertically ,
266+ horizontalArrangement = Arrangement .spacedBy(style.actionSpacing, Alignment .End ),
267+ modifier = Modifier
268+ .align(Alignment .CenterEnd )
269+ .padding(start = 8 .dp)
270+ .widthIn(min = style.actionsMinWidth)
271+ .onGloballyPositioned { actionsWidthPx = it.size.width },
272+ ) {
273+ actions()
274+ }
247275 }
248-
249- // Right section: Actions
276+ } else {
250277 Row (
278+ modifier = outerModifier.padding(horizontal = style.horizontalPadding),
251279 verticalAlignment = Alignment .CenterVertically ,
252- horizontalArrangement = Arrangement .spacedBy(style.actionSpacing, Alignment .End ),
253- modifier = Modifier .padding(start = 8 .dp).widthIn(min = style.actionsMinWidth),
254280 ) {
255- actions()
281+ // Left section: Nav Actions
282+ Row (
283+ verticalAlignment = Alignment .CenterVertically ,
284+ modifier = Modifier .padding(end = 8 .dp).widthIn(min = navigationActionsMinWidth),
285+ ) {
286+ navigationActions()
287+ }
288+
289+ // Center section: Title
290+ Box (
291+ modifier = Modifier
292+ .weight(1f )
293+ .fillMaxHeight()
294+ .graphicsLayer { alpha = if (showsTitle) 1f else 0f },
295+ contentAlignment = Alignment .Center ,
296+ ) {
297+ title()
298+ }
299+
300+ // Right section: Actions
301+ Row (
302+ verticalAlignment = Alignment .CenterVertically ,
303+ horizontalArrangement = Arrangement .spacedBy(style.actionSpacing, Alignment .End ),
304+ modifier = Modifier .padding(start = 8 .dp).widthIn(min = style.actionsMinWidth),
305+ ) {
306+ actions()
307+ }
256308 }
257309 }
258310 }
0 commit comments