package cc.unitmesh.devins.ui.compose.chat import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.changedToUp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import cc.unitmesh.agent.AgentType import cc.unitmesh.devins.ui.compose.icons.AutoDevComposeIcons import cc.unitmesh.devins.ui.state.UIStateManager import kotlin.math.roundToInt /** * Desktop 标题栏(VSCode 风格) * * 布局: * - 左侧:Agent Type 菜单(未选中=背景色,选中=圆角突出) * - 中间:(保留用于将来扩展) * - 右侧:Sidebar Toggle + Project Explorer + Settings + Tools */ @Composable fun DesktopTitleBarTabs( currentAgentType: AgentType, onAgentTypeChange: (AgentType) -> Unit, onConfigureRemote: () -> Unit = {}, onDoubleClick: () -> Unit = {}, modifier: Modifier = Modifier ) { LaunchedEffect(currentAgentType) { if (currentAgentType == AgentType.CODE_REVIEW || currentAgentType == AgentType.KNOWLEDGE || currentAgentType == AgentType.WEB_EDIT ) { UIStateManager.setSessionSidebarVisible(false) } else { UIStateManager.setSessionSidebarVisible(true) } } // 从全局状态获取 val workspacePath by UIStateManager.workspacePath.collectAsState() val isTreeViewVisible by UIStateManager.isTreeViewVisible.collectAsState() val isSessionSidebarVisible by UIStateManager.isSessionSidebarVisible.collectAsState() val contentFontScale by UIStateManager.contentFontScale.collectAsState() Box( modifier = modifier .fillMaxWidth() .height(40.dp) .padding(horizontal = 8.dp, vertical = 4.dp) .pointerInput(Unit) { awaitPointerEventScope { var lastClickTime = 0L while (true) { val event = awaitPointerEvent(androidx.compose.ui.input.pointer.PointerEventPass.Main) val change = event.changes.firstOrNull() ?: continue if (change.changedToUp()) { val currentTime = event.changes.first().uptimeMillis if (currentTime - lastClickTime < 500) { onDoubleClick() } lastClickTime = currentTime } } } } ) { // Center: Workspace Indicator if (workspacePath.isNotEmpty()) { Surface( modifier = Modifier .align(Alignment.Center) .height(28.dp) .padding(horizontal = 8.dp), shape = RoundedCornerShape(6.dp), color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) ) { Row( modifier = Modifier .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp) ) { Icon( imageVector = AutoDevComposeIcons.Folder, contentDescription = null, modifier = Modifier.size(14.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant ) Text( text = workspacePath.substringAfterLast('/').ifEmpty { workspacePath }, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis ) } } } // Left and Right content Row( modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { // Left: Sidebar Toggle + Agent Tabs Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically ) { // Sidebar Toggle (Left side) IconButton( onClick = { UIStateManager.toggleSessionSidebar() }, modifier = Modifier.size(28.dp) ) { Icon( imageVector = if (isSessionSidebarVisible) AutoDevComposeIcons.MenuOpen else AutoDevComposeIcons.Menu, contentDescription = if (isSessionSidebarVisible) "Collapse Sidebar" else "Expand Sidebar", tint = if (isSessionSidebarVisible) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.onSurface }, modifier = Modifier.size(16.dp) ) } // Divider Surface( modifier = Modifier .width(1.dp) .height(20.dp), color = MaterialTheme.colorScheme.outlineVariant ) {} (AgentType.entries - AgentType.LOCAL_CHAT - AgentType.WEB_EDIT) .forEach { type -> AgentTypeMenuItem( type = type, isSelected = type == currentAgentType, onClick = { onAgentTypeChange(type) } ) } } Row( horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { AgentTypeMenuItem( type = AgentType.WEB_EDIT, isSelected = false, onClick = { onAgentTypeChange(AgentType.WEB_EDIT) } ) TextButton( onClick = { UIStateManager.toggleTreeView() }, contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), modifier = Modifier.height(28.dp) ) { Text( text = if (isTreeViewVisible) "Hide Files" else "Files", style = MaterialTheme.typography.labelSmall, color = if (isTreeViewVisible) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.onSurface } ) } ContentFontScaleControl( contentFontScale = contentFontScale, modifier = Modifier.height(28.dp) ) } } } } @Composable private fun ContentFontScaleControl( contentFontScale: Float, modifier: Modifier = Modifier ) { val percent = (contentFontScale * 100f).roundToInt() val canDecrease = contentFontScale > UIStateManager.CONTENT_FONT_SCALE_MIN + 0.001f val canIncrease = contentFontScale < UIStateManager.CONTENT_FONT_SCALE_MAX - 0.001f Surface( modifier = modifier, shape = RoundedCornerShape(6.dp), color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), ) { Row( modifier = Modifier.padding(horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp) ) { IconButton( onClick = { UIStateManager.decreaseContentFontScale() }, enabled = canDecrease, modifier = Modifier.size(28.dp) ) { Icon( imageVector = AutoDevComposeIcons.ZoomOut, contentDescription = "Decrease font size", modifier = Modifier.size(16.dp), tint = if (canDecrease) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) } Text( text = "${percent}%", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1 ) IconButton( onClick = { UIStateManager.increaseContentFontScale() }, enabled = canIncrease, modifier = Modifier.size(28.dp) ) { Icon( imageVector = AutoDevComposeIcons.ZoomIn, contentDescription = "Increase font size", modifier = Modifier.size(16.dp), tint = if (canIncrease) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) } } } } @Composable private fun AgentTypeMenuItem( type: AgentType, isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { val backgroundColor = if (isSelected) { MaterialTheme.colorScheme.primaryContainer } else { MaterialTheme.colorScheme.surface } val contentColor = if (isSelected) { MaterialTheme.colorScheme.onPrimaryContainer } else { MaterialTheme.colorScheme.onSurfaceVariant } Surface( modifier = modifier .height(28.dp) .clip(RoundedCornerShape(6.dp)), shape = RoundedCornerShape(6.dp), onClick = onClick, color = backgroundColor, tonalElevation = if (isSelected) 2.dp else 0.dp ) { Row( modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = type.getDisplayName(), style = MaterialTheme.typography.labelSmall, color = contentColor, fontSize = MaterialTheme.typography.labelSmall.fontSize ) } } }