From 0455d1f8c6b438deebbbc0cd632aaf575e0f9725 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Sun, 19 Apr 2026 05:21:12 +0300 Subject: [PATCH] feat(desktop): single-pane workspaces store nav bar screen list Change Workspace.singlePaneScreen (single string) to singlePaneScreens (list of typeKey strings). First screen is the default. On workspace switch, loads the screen list into PinnedNavBarState and navigates to the first screen. - Add SinglePaneScreensEditor to workspace editor dialog - Add PinnedNavBarState.loadFromList() for workspace-driven nav - Backward compat: load old "singlePaneScreen" format as single-item list - Cmd+Shift+S captures current deck columns as singlePaneScreens Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vitorpamplona/amethyst/desktop/Main.kt | 23 ++++-- .../amethyst/desktop/ui/deck/AppDrawer.kt | 74 +++++++++++++------ .../desktop/ui/deck/PinnedNavBarState.kt | 5 ++ .../amethyst/desktop/ui/deck/Workspace.kt | 2 +- .../desktop/ui/deck/WorkspaceManager.kt | 13 +++- 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt index 3a76d9e141..bbac3c0269 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt @@ -267,6 +267,12 @@ fun main() { iconName = "Star", layoutMode = layoutMode, columns = columns, + singlePaneScreens = + if (layoutMode == LayoutMode.SINGLE_PANE) { + columns.map { it.typeKey } + } else { + emptyList() + }, ) workspaceManager.addWorkspace(ws) } @@ -789,12 +795,17 @@ fun App( } LayoutMode.SINGLE_PANE -> { - val screenKey = - ws.singlePaneScreen - ?: ws.columns.firstOrNull()?.typeKey - ?: "home" - val type = DeckState.parseColumnTypeFromKey(screenKey) - if (type != null) singlePaneState.navigate(type) + // Set nav bar screens from workspace + if (ws.singlePaneScreens.isNotEmpty()) { + val screens = + ws.singlePaneScreens.mapNotNull { + DeckState.parseColumnTypeFromKey(it) + } + if (screens.isNotEmpty()) { + pinnedNavBarState.loadFromList(screens) + singlePaneState.navigate(screens.first()) + } + } } } }, diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/AppDrawer.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/AppDrawer.kt index 5a004bba4a..0adca7e118 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/AppDrawer.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/AppDrawer.kt @@ -55,6 +55,7 @@ import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.PushPin import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.SportsEsports +import androidx.compose.material.icons.filled.Star import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenu @@ -883,7 +884,9 @@ private fun WorkspaceEditorDialog( var columns by remember { mutableStateOf(initial?.columns ?: listOf(Workspace.WorkspaceColumn("home"))) } - var singlePaneScreen by remember { mutableStateOf(initial?.singlePaneScreen) } + var singlePaneScreens by remember { + mutableStateOf(initial?.singlePaneScreens ?: listOf("home")) + } AlertDialog( onDismissRequest = onDismiss, @@ -941,11 +944,11 @@ private fun WorkspaceEditorDialog( if (layoutMode == LayoutMode.DECK) { DeckColumnEditor(columns = columns, onColumnsChange = { columns = it }) } - // Single Pane: screen picker + // Single Pane: nav bar screen selector (first = default) if (layoutMode == LayoutMode.SINGLE_PANE) { - SinglePaneScreenPicker( - selected = singlePaneScreen, - onSelect = { singlePaneScreen = it }, + SinglePaneScreensEditor( + screens = singlePaneScreens, + onScreensChange = { singlePaneScreens = it }, ) } } @@ -963,7 +966,7 @@ private fun WorkspaceEditorDialog( iconName = iconName, layoutMode = layoutMode, columns = columns, - singlePaneScreen = singlePaneScreen, + singlePaneScreens = singlePaneScreens, ), ) }, @@ -1021,29 +1024,52 @@ private fun DeckColumnEditor( } @Composable -private fun SinglePaneScreenPicker( - selected: String?, - onSelect: (String) -> Unit, +private fun SinglePaneScreensEditor( + screens: List, + onScreensChange: (List) -> Unit, ) { - var expanded by remember { mutableStateOf(false) } - val selectedTitle = - selected?.let { DeckState.parseColumnTypeFromKey(it)?.title() } ?: "Select screen" - Column { - Text("Screen", style = MaterialTheme.typography.labelMedium) + Text("Nav Bar Screens", style = MaterialTheme.typography.labelMedium) + Text( + "First screen is the default", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) Spacer(Modifier.height(4.dp)) - TextButton(onClick = { expanded = true }) { Text(selectedTitle) } - DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - LAUNCHABLE_SCREENS.filter { !it.requiresInput() }.forEach { screen -> - DropdownMenuItem( - text = { Text(screen.title()) }, - onClick = { - onSelect(screen.typeKey()) - expanded = false - }, - ) + screens.forEachIndexed { idx, key -> + val displayName = DeckState.parseColumnTypeFromKey(key)?.title() ?: key + Row(verticalAlignment = Alignment.CenterVertically) { + if (idx == 0) { + Icon( + Icons.Default.Star, + "Default", + Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Spacer(Modifier.width(4.dp)) + } + Text(displayName, Modifier.weight(1f)) + IconButton(onClick = { + onScreensChange(screens.toMutableList().apply { removeAt(idx) }) + }) { Icon(Icons.Default.Close, "Remove", Modifier.size(16.dp)) } } } + // Add screen dropdown + var expanded by remember { mutableStateOf(false) } + TextButton(onClick = { expanded = true }) { Text("+ Add Screen") } + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + PinnedNavBarState.PINNABLE_SCREENS + .filter { screen -> screens.none { it == screen.typeKey() } } + .forEach { screen -> + DropdownMenuItem( + text = { Text(screen.title()) }, + onClick = { + onScreensChange(screens + screen.typeKey()) + expanded = false + }, + ) + } + } } } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/PinnedNavBarState.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/PinnedNavBarState.kt index fe9252dd4c..00d5f5eb92 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/PinnedNavBarState.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/PinnedNavBarState.kt @@ -67,6 +67,11 @@ class PinnedNavBarState { DesktopPreferences.pinnedNavItems = _pinnedScreens.value.joinToString(",") { it.typeKey() } } + fun loadFromList(screens: List) { + _pinnedScreens.value = screens.ifEmpty { DEFAULT_PINNED } + save() + } + fun load() { val raw = DesktopPreferences.pinnedNavItems if (raw.isBlank()) { diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/Workspace.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/Workspace.kt index 0c3e4f801f..0e2d2c62ab 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/Workspace.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/Workspace.kt @@ -47,7 +47,7 @@ data class Workspace( val iconName: String, val layoutMode: LayoutMode, val columns: List, - val singlePaneScreen: String? = null, + val singlePaneScreens: List = emptyList(), ) { data class WorkspaceColumn( val typeKey: String, diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/WorkspaceManager.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/WorkspaceManager.kt index d4edbd1e90..2de3e9cf49 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/WorkspaceManager.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/WorkspaceManager.kt @@ -124,7 +124,7 @@ class WorkspaceManager( "name" to ws.name, "iconName" to ws.iconName, "layoutMode" to ws.layoutMode.name, - "singlePaneScreen" to ws.singlePaneScreen, + "singlePaneScreens" to ws.singlePaneScreens, "columns" to ws.columns.map { col -> mapOf( @@ -162,7 +162,14 @@ class WorkspaceManager( } catch (e: Exception) { LayoutMode.DECK } - val singlePaneScreen = entry["singlePaneScreen"] as? String + + @Suppress("UNCHECKED_CAST") + val singlePaneScreens = + (entry["singlePaneScreens"] as? List) ?: run { + // Backward compat: old format had single "singlePaneScreen" string + val legacy = entry["singlePaneScreen"] as? String + if (legacy != null) listOf(legacy) else emptyList() + } @Suppress("UNCHECKED_CAST") val columns = @@ -183,7 +190,7 @@ class WorkspaceManager( iconName = iconName, layoutMode = layoutMode, columns = columns, - singlePaneScreen = singlePaneScreen, + singlePaneScreens = singlePaneScreens, ) } catch (e: Exception) { null