diff --git a/packagesearch.versions.toml b/packagesearch.versions.toml index 5a21e761..64c1e6cd 100644 --- a/packagesearch.versions.toml +++ b/packagesearch.versions.toml @@ -37,6 +37,7 @@ ij-platform-ide-core = { module = "com.jetbrains.intellij.platform:ide-core", ve ij-platform-ide-impl = { module = "com.jetbrains.intellij.platform:ide-impl", version.ref = "idea" } jewel-ui = { module = "org.jetbrains.jewel:jewel-ui", version.ref = "jewel" } jewel-foundation = { module = "org.jetbrains.jewel:jewel-foundation", version.ref = "jewel" } +jewel-standalone = { module= "org.jetbrains.jewel:jewel-int-ui-standalone", version.ref = "jewel" } jewel-bridge-ij232 = { module = "org.jetbrains.jewel:jewel-ide-laf-bridge", version.ref = "jewel-bridge-232" } jewel-bridge-ij233 = { module = "org.jetbrains.jewel:jewel-ide-laf-bridge", version.ref = "jewel-bridge-233" } compose-desktop-components-splitpane = { module = "org.jetbrains.compose.components:components-splitpane", version.ref = "composeDesktop" } diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchColors.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchColors.kt new file mode 100644 index 00000000..01beec23 --- /dev/null +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchColors.kt @@ -0,0 +1,17 @@ +package com.jetbrains.packagesearch.plugin.ui + +import androidx.compose.ui.graphics.Color +import com.jetbrains.packagesearch.plugin.ui.bridge.pickComposeColorFromLaf + +object PackageSearchColors { + object Backgrounds { + fun packageItemHeader(): Color = + pickComposeColorFromLaf("ToolWindow.HeaderTab.selectedInactiveBackground") + + + fun attributeBadge() = packageItemHeader() + + } + + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchPackagePanel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchPackagePanel.kt index 86143fb4..ef36d5ec 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchPackagePanel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/PackageSearchPackagePanel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import com.intellij.ui.JBColor import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModule import com.jetbrains.packagesearch.plugin.ui.bridge.packageSearchSplitter diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Components.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Components.kt index 92198ed7..abe57e57 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Components.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Components.kt @@ -4,7 +4,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.onClick +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -23,9 +26,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.intellij.icons.AllIcons +import com.jetbrains.packagesearch.plugin.ui.PackageSearchColors import com.jetbrains.packagesearch.plugin.ui.PackageSearchMetrics import java.awt.Cursor -import org.jetbrains.compose.splitpane.SplitPaneScope import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalTextStyle import org.jetbrains.jewel.ui.component.DropdownLink @@ -34,6 +37,7 @@ import org.jetbrains.jewel.ui.component.IconButton import org.jetbrains.jewel.ui.component.MenuScope import org.jetbrains.jewel.ui.component.PopupMenu import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.compose.splitpane.SplitPaneScope @Composable fun LabelInfo( @@ -141,7 +145,6 @@ internal fun PackageActionPopup( } - internal fun SplitPaneScope.packageSearchSplitter( splitterColor: Color, cursor: PointerIcon = PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)), @@ -171,3 +174,43 @@ internal fun SplitPaneScope.packageSearchSplitter( } } } + + +@Composable +internal fun AttributeBadge(text: String, onClick: () -> Unit) { + val isDark = JewelTheme.isDark + val background = remember(isDark) { + PackageSearchColors.Backgrounds.attributeBadge() + } + + Box( + modifier = Modifier + .background(color = background, shape = RoundedCornerShape(12.dp)) + .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) + .onClick { onClick() }, + ) { + Text( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp), text = text, + ) + } + +} + +//@Preview +//@Composable +//internal fun AttributeBadgePreview() { +// Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { +// IntUiTheme { +// Box(Modifier.background(LocalGlobalColors.current.paneBackground).padding(16.dp)) { +// AttributeBadge(text = "Android") {} +// } +// } +// IntUiTheme(true) { +// Box(Modifier.background(LocalGlobalColors.current.paneBackground).padding(16.dp)) { +// AttributeBadge(text = "Android") {} +// } +// } +// } +// +// +//} \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Utils.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Utils.kt index 772738c9..a81e88f3 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Utils.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/bridge/Utils.kt @@ -41,4 +41,3 @@ fun isLightTheme(): Boolean { private fun Color.getBrightness() = (red * 299 + green * 587 + blue * 114) / 1000 -fun Modifier.pointerChangeToHandModifier() = this.pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/Utils.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/Utils.kt index a72338df..8fc1f628 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/Utils.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/Utils.kt @@ -32,4 +32,4 @@ internal val Project.isProjectImportingFlow trySend(false) } } - }.withInitialValue(false) \ No newline at end of file + }.withInitialValue(false) diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt index b95b719b..d4004664 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt @@ -2,6 +2,7 @@ package com.jetbrains.packagesearch.plugin.ui.model.infopanel import com.jetbrains.packagesearch.plugin.core.data.IconProvider import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModule +import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem sealed interface InfoPanelContent { @@ -140,4 +141,21 @@ sealed interface InfoPanelContent { } } + sealed interface Attributes : InfoPanelContent{ + val attributes: List + + data class FromVariant( + override val tabTitle: String, + val variantName: String, + override val attributes: List, + ) : Attributes + + data class FromSearch( + override val tabTitle: String, + override val attributes: List, + val defaultSourceSet: String, + val additionalSourceSets: List, + ) : Attributes + } + } \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContentEvent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContentEvent.kt index 79f5a506..28da25d2 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContentEvent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContentEvent.kt @@ -2,14 +2,14 @@ package com.jetbrains.packagesearch.plugin.ui.model.infopanel import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDeclaredPackage import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModule +import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem import org.jetbrains.packagesearch.api.v3.ApiPackage sealed interface InfoPanelContentEvent { - val module: PackageSearchModule - sealed interface Package : InfoPanelContentEvent { + val module: PackageSearchModule val packageListId: PackageListItem.Package.Id @@ -51,5 +51,20 @@ sealed interface InfoPanelContentEvent { } } + sealed interface Attributes : InfoPanelContentEvent { + val attributes: List + + data class FromVariant( + val variantName: String, + override val attributes: List, + ) : Attributes + + data class FromSearch( + val defaultVariant: String, + val additionalVariants: List, + override val attributes: List, + ) : Attributes + + } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt index bbb34cbf..c008b068 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDeclaredPackage import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModule +import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListViewModel import com.jetbrains.packagesearch.plugin.utils.PackageSearchProjectService @@ -62,6 +63,14 @@ class InfoPanelViewModel( } } } + + is InfoPanelContentEvent.Attributes.FromVariant -> { + event.asPanelContent() + } + + is InfoPanelContentEvent.Attributes.FromSearch -> { + event.asPanelContent() + } } } .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) @@ -134,6 +143,32 @@ class InfoPanelViewModel( ) } + fun setDeclaredHeaderAttributes( + variantName: String, + attributes: List, + ) { + setDataEventChannel.trySend( + InfoPanelContentEvent.Attributes.FromVariant( + variantName = variantName, + attributes = attributes + ) + ) + } + + fun setSearchHeaderAttributes( + defaultVariant: String, + additionalVariants: List, + attributes: List, + ) { + setDataEventChannel.trySend( + InfoPanelContentEvent.Attributes.FromSearch( + defaultVariant = defaultVariant, + additionalVariants = additionalVariants, + attributes = attributes + ) + ) + } + } diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/Utils.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/Utils.kt index 3d237a05..b62d6d07 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/Utils.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/Utils.kt @@ -1,6 +1,7 @@ package com.jetbrains.packagesearch.plugin.ui.model.infopanel import com.jetbrains.packagesearch.plugin.PackageSearchBundle.message +import org.jetbrains.jewel.foundation.lazy.tree.buildTree import org.jetbrains.packagesearch.api.v3.ApiGitHub import org.jetbrains.packagesearch.api.v3.ApiScm import org.jetbrains.packagesearch.api.v3.LicenseFile @@ -24,4 +25,5 @@ internal fun Licenses<*>.asInfoPanelLicenseList() = buildList { internal fun LicenseFile.toInfoPanelLicense(): InfoPanelContent.PackageInfo.License? { val name = name ?: url ?: return null return InfoPanelContent.PackageInfo.License(name, url) -} \ No newline at end of file +} + diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt index f2dc380a..61b0f4e5 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt @@ -126,6 +126,11 @@ internal fun InfoPanelContentEvent.Package.Declared.WithVariant.asPanelContent( declaredVariant = variantName, allowMissingScope = !module.dependencyMustHaveAScope, variantTerminology = module.variantTerminology + ), + InfoPanelContent.Attributes.FromVariant( + variantName = variantName, + tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), + attributes = module.variants.getValue(variantName).attributes ) ) @@ -152,6 +157,11 @@ internal fun InfoPanelContentEvent.Package.Remote.WithVariants.asPanelContent( isLoading = isLoading, isInstalledInPrimaryVariant = module.variants.getValue(primaryVariantName).declaredDependencies .any { it.id == apiPackage.id } + ), + InfoPanelContent.Attributes.FromVariant( + tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), + variantName = primaryVariantName, + attributes = module.variants.getValue(primaryVariantName).attributes ) ) @@ -175,4 +185,20 @@ internal fun InfoPanelContentEvent.Package.Remote.Base.asPanelContent( repositories = apiPackage.repositories(), isLoading = isLoading ) +) + +internal fun InfoPanelContentEvent.Attributes.FromVariant.asPanelContent() = listOf( + InfoPanelContent.Attributes.FromVariant( + variantName = variantName, + tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), + attributes = attributes, + ) +) +internal fun InfoPanelContentEvent.Attributes.FromSearch.asPanelContent() = listOf( + InfoPanelContent.Attributes.FromSearch( + tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.attributes"), + defaultSourceSet = defaultVariant, + additionalSourceSets = additionalVariants, + attributes = attributes, + ) ) \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItemEvent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItemEvent.kt index 1a0f27f7..62233de8 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItemEvent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListItemEvent.kt @@ -21,7 +21,19 @@ sealed interface PackageListItemEvent { sealed interface InfoPanelEvent : PackageListItemEvent { @Serializable - data class OnHeaderAttributesClick(override val eventId: PackageListItem.Header.Id) : InfoPanelEvent + sealed interface OnHeaderAttributesClick : InfoPanelEvent { + @Serializable + data class DeclaredHeaderAttributesClick( + override val eventId: PackageListItem.Header.Id.Declared, + val variantName: String, + ) : OnHeaderAttributesClick + @Serializable + data class SearchHeaderAttributesClick( + override val eventId: PackageListItem.Header.Id.Remote, + val attributesNames: List + ) : OnHeaderAttributesClick + + } @Serializable data class OnHeaderVariantsClick(override val eventId: PackageListItem.Header.Id) : InfoPanelEvent @@ -31,6 +43,11 @@ sealed interface PackageListItemEvent { @Serializable data class OnPackageDoubleClick(override val eventId: PackageListItem.Id) : InfoPanelEvent + + @Serializable + data class OnSelectedPackageClick(override val eventId: PackageListItem.Id) : InfoPanelEvent + + } @Serializable @@ -87,9 +104,6 @@ sealed interface PackageListItemEvent { } - - - @Serializable data class Update(override val eventId: PackageListItem.Package.Declared.Id) : OnPackageAction diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListViewModel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListViewModel.kt index 5b6fd371..5a56b3b9 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListViewModel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/packageslist/PackageListViewModel.kt @@ -310,6 +310,7 @@ class PackageListViewModel( is PackageListItemEvent.InfoPanelEvent.OnHeaderVariantsClick -> handle(event) is PackageListItemEvent.InfoPanelEvent.OnPackageSelected -> handle(event) is PackageListItemEvent.InfoPanelEvent.OnPackageDoubleClick -> handle(event) + is PackageListItemEvent.InfoPanelEvent.OnSelectedPackageClick -> handle(event) is PackageListItemEvent.OnPackageAction.GoToSource -> handle(event) is PackageListItemEvent.OnPackageAction.Install.Base -> handle(event) is PackageListItemEvent.OnPackageAction.Install.WithVariant -> handle(event) @@ -331,6 +332,12 @@ class PackageListViewModel( logInfoPanelOpened() } + private fun handle(event: PackageListItemEvent.InfoPanelEvent.OnSelectedPackageClick) { + if (event.eventId is PackageListItem.Package.Id) handle( + PackageListItemEvent.InfoPanelEvent.OnPackageSelected(event.eventId) + ) + } + private fun handle(event: PackageListItemEvent.InfoPanelEvent.OnPackageSelected) { val infoPanelViewModel = project.service() logPackageSelected(event.eventId is PackageListItem.Package.Declared.Id) @@ -591,13 +598,46 @@ class PackageListViewModel( } } - private fun handle(event: PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick) { - logTODO() - logHeaderAttributesClick( - isSearchHeader = event.eventId is PackageListItem.Header.Id.Remote - ) + private suspend fun handle(event: PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick) { + logHeaderAttributesClick(isSearchHeader = event.eventId is PackageListItem.Header.Id.Remote) + val infoPanelViewModel = project.service() + + val module = event.eventId.getModule() as? PackageSearchModule.WithVariants ?: return + + when (event) { + is PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick.DeclaredHeaderAttributesClick -> { + val attributes = module.variants[event.variantName]?.attributes ?: return + infoPanelViewModel.setDeclaredHeaderAttributes(event.variantName, attributes = attributes) + } + + is PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick.SearchHeaderAttributesClick -> { + val attributes = module + .variants + .map { it.value.attributes } + .flatten() + .filter { it.value in event.attributesNames } + .distinct() + + val variants = module.variants.values.map { it.name } - module.mainVariantName + + infoPanelViewModel.setSearchHeaderAttributes( + defaultVariant = module.mainVariantName, + additionalVariants = variants, + attributes = attributes + ) + } + } + + + project.service().isInfoPanelOpen.let { openStateFlow -> + if (!openStateFlow.value) { + openStateFlow.emit(true) + } + } + } + private suspend fun handle(event: PackageListItemEvent.EditPackageEvent.SetVariant) { val module = event.eventId .getModule() as? PackageSearchModule.WithVariants diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt index 85be9f20..2a745c06 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/PackageSearchPackageList.kt @@ -131,7 +131,12 @@ internal fun SelectableLazyItemScope.PackageListItem( .onClick( interactionSource = remember { MutableInteractionSource() }, onDoubleClick = { onPackageListItemEvent(OnPackageDoubleClick(content.id)) }, - onClick = { } + onClick = { + //this event should be handled when you click on a selected package to refresh side panel content + if (isSelected ) onPackageListItemEvent( + PackageListItemEvent.InfoPanelEvent.OnSelectedPackageClick(content.id) + ) + } ), ) { Row( diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/items/PackageGroupHeader.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/items/PackageGroupHeader.kt index e13e1981..3151cb0f 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/items/PackageGroupHeader.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/packages/items/PackageGroupHeader.kt @@ -1,7 +1,6 @@ package com.jetbrains.packagesearch.plugin.ui.panels.packages.items import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.SpaceBetween import androidx.compose.foundation.layout.Box @@ -11,6 +10,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.onClick import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -20,17 +23,19 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.intellij.icons.AllIcons import com.jetbrains.packagesearch.plugin.PackageSearchBundle.message +import com.jetbrains.packagesearch.plugin.ui.PackageSearchColors import com.jetbrains.packagesearch.plugin.ui.bridge.LabelInfo -import com.jetbrains.packagesearch.plugin.ui.bridge.pickComposeColorFromLaf import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem.Header.State import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItemEvent import java.awt.Cursor +import org.jetbrains.jewel.foundation.modifier.onHover import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.CircularProgressIndicator import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.Link import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.theme.linkStyle @Composable fun PackageListHeader( @@ -38,12 +43,10 @@ fun PackageListHeader( content: PackageListItem.Header, onEvent: (PackageListItemEvent) -> Unit, ) { - val backgroundColor = - if (JewelTheme.isDark) { - pickComposeColorFromLaf("ToolWindow.HeaderTab.selectedInactiveBackground") - } else { - pickComposeColorFromLaf("Tree.selectionInactiveBackground") - } + val isDarkTheme = JewelTheme.isDark + val backgroundColor = remember(isDarkTheme) { + PackageSearchColors.Backgrounds.packageItemHeader() + } Row( modifier = Modifier @@ -88,22 +91,40 @@ fun PackageListHeader( } Text( - fontWeight = FontWeight(600), + fontWeight = FontWeight.ExtraBold, text = content.title, maxLines = 1 ) } if (content.attributes.isNotEmpty()) { + var attributeTextColor by remember { mutableStateOf(Color.Unspecified) } + val linkTextColor = JewelTheme.linkStyle.colors.content Box( modifier = Modifier .onClick { - onEvent(PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick(content.id)) + val event = + when (content.id) { + is PackageListItem.Header.Id.Declared.Base -> return@onClick + is PackageListItem.Header.Id.Remote -> PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick.SearchHeaderAttributesClick( + eventId = content.id, + attributesNames = content.attributes + ) + is PackageListItem.Header.Id.Declared.WithVariant -> PackageListItemEvent.InfoPanelEvent.OnHeaderAttributesClick.DeclaredHeaderAttributesClick( + eventId = content.id, + variantName = content.title, + ) + } + onEvent(event) + } + .onHover { + attributeTextColor = if (it) linkTextColor else Color.Unspecified } .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))), contentAlignment = Alignment.Center, ) { - LabelInfo( + Text( text = content.attributes.joinToString(" "), + color = attributeTextColor, maxLines = 1 ) } diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt new file mode 100644 index 00000000..2715203a --- /dev/null +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt @@ -0,0 +1,286 @@ +package com.jetbrains.packagesearch.plugin.ui.panels.side + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.jetbrains.packagesearch.plugin.PackageSearchBundle +import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant +import com.jetbrains.packagesearch.plugin.ui.bridge.AttributeBadge +import com.jetbrains.packagesearch.plugin.ui.bridge.LabelInfo +import com.jetbrains.packagesearch.plugin.ui.model.infopanel.InfoPanelContent +import kotlin.math.roundToInt +import kotlin.random.Random +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.jewel.ui.component.Text + +@Composable +fun HeaderAttributesTab( + content: InfoPanelContent.Attributes, + scrollState: ScrollState, +) { + HeaderAttributesTabImpl( + content = content, + scrollState = scrollState, + contentTitle = PackageSearchBundle.message("packagesearch.ui.toolwindow.sidepanel.searchResultSupport"), + attributeTypeName = PackageSearchBundle.message("packagesearch.ui.toolwindow.sidepanel.platformsList"), + sourceSetString = PackageSearchBundle.message("packagesearch.ui.toolwindow.sidepanel.searchResultSupport.sourceSets"), + ) +} + +@Composable +private fun HeaderAttributesTabImpl( + content: InfoPanelContent.Attributes, + contentTitle: String, + scrollState: ScrollState, + attributeTypeName: String, + sourceSetString: String, +) { + val scope = rememberCoroutineScope() + val attributes = content.attributes + // Global positions of attributes will be used to store the y offset used on scrollToItem + val attributeGlobalPositionMap = remember { mutableMapOf() } + + Column(verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(12.dp)) { + if (content is InfoPanelContent.Attributes.FromSearch) { + Text( + text = contentTitle, + fontSize = 14.sp, + fontWeight = FontWeight.ExtraBold, + modifier = Modifier.padding(top = 4.dp) + ) + } + + + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + attributes.forEachIndexed { index, attribute -> + AttributeBadge(text = attribute.value) { + scope.scrollToAttribute(scrollState, attributeGlobalPositionMap, index) + } + } + } + + if (content is InfoPanelContent.Attributes.FromSearch) { + SourceSetsList(content, sourceSetString) + } + + AttributeItems(attributeTypeName = attributeTypeName, attributes, attributeGlobalPositionMap) + } +} + + +@Composable +private fun ColumnScope.SourceSetsList( + content: InfoPanelContent.Attributes.FromSearch, + title: String, +) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + text = title, + fontSize = 14.sp, + fontWeight = FontWeight.ExtraBold, + modifier = Modifier.padding(top = 4.dp) + ) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Text(text = content.defaultSourceSet) + LabelInfo(PackageSearchBundle.message("packagesearch.ui.toolwindow.variant.default.text")) + } + Text(text = content.additionalSourceSets.joinToString(", ")) + + } +} + +@Composable +private fun AttributeItems( + attributeTypeName: String, + attributesName: List, + attributeGlobalPosition: MutableMap, +) { + Text( + text = attributeTypeName, + fontSize = 14.sp, + fontWeight = FontWeight.ExtraBold, + modifier = Modifier.padding(top = 4.dp) + ) + + attributesName.forEachIndexed { index, attribute -> + AttributeItem( + modifier = Modifier.onGloballyPositioned { + attributeGlobalPosition[index] = it.positionInParent().y.roundToInt() + }, + attributeName = attribute.value, + nestedAttributesName = attribute.flatten() + ) + } +} + +private fun PackageSearchModuleVariant.Attribute.flatten(): List = + when (this) { + is PackageSearchModuleVariant.Attribute.NestedAttribute -> children.flatMap { it.flatten() } + is PackageSearchModuleVariant.Attribute.StringAttribute -> listOf(value) + } + + +private fun CoroutineScope.scrollToAttribute( + scrollState: ScrollState, + attributeGlobalPosition: MutableMap, + index: Int, +) { + launch { + scrollState.animateScrollTo( + value = attributeGlobalPosition[index] ?: 0, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessVeryLow + ) + ) + } +} + +@Composable +fun AttributeItem(modifier: Modifier = Modifier, attributeName: String, nestedAttributesName: List) { + + Row(modifier = modifier, verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Row( + modifier = Modifier.width(160.dp), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text(text = attributeName) + LabelInfo(nestedAttributesName.size.toString()) + } + + Text(text = nestedAttributesName.joinToString("\n")) + } +} + + +//@Preview +//@Composable +//private fun HeaderAttributesPreviewTab() { +// val activeTabMock = InfoPanelContent.Attributes.FromVariant( +// tabTitle = "FromVariant", +// variantName = "jvm", +// attributes = generateAttributesMock() +// ) +// val scrollStateMock = ScrollState(0) +// Column(Modifier.padding(8.dp)) { +// IntUiTheme(true) { +// Column(Modifier.background(LocalGlobalColors.current.paneBackground).padding(16.dp)) { +// HeaderAttributesTabImpl( +// content = activeTabMock, +// scrollState = scrollStateMock, +// contentTitle = "FromSearch results that support:", +// attributeTypeName = "Platforms:", +// sourceSetString = "Source Sets:" +// ) +// +// } +// +// Divider(orientation = Orientation.Horizontal, modifier = Modifier.padding(vertical = 16.dp)) +// +// } +// +// +// IntUiTheme(false) { +// Column(Modifier.background(LocalGlobalColors.current.paneBackground).padding(16.dp)) { +// HeaderAttributesTabImpl( +// content = activeTabMock, +// scrollState = scrollStateMock, +// contentTitle = "FromSearch results that support:", +// attributeTypeName = "Platforms:", +// sourceSetString = "Source Sets:" +// ) +// +// } +// } +// } +//} + +private fun generateAttributesMock(): List { + val attributes = mutableListOf() + + platformListMock.take(Random.nextInt(2, 10)).forEach { + attributes.add( + if (Random.nextBoolean()) { + PackageSearchModuleVariant.Attribute.NestedAttribute( + it, + platformListMock.take(Random.nextInt(2, 10)).map { + PackageSearchModuleVariant.Attribute.StringAttribute(it) + } + ) + + + } else { + + PackageSearchModuleVariant.Attribute.StringAttribute(it) + } + ) + } + + return attributes + +} + +internal val platformListMock get() = buildList { + add("Android") + add("android") + add("Apple") + add("iOS") + add("iosX64") + add("iosArm64") + add("iosSimulatorArm64") + add("macOS") + add("macosArm64") + add("macosX64") + + add("watchOS") + add("watchosArm32") + add("watchosArm64") + add("watchosX64") + add("watchosSimulatorArm64") + + add("tvOS") + add("tvosX64") + add("tvosArm64") + add("tvosSimulatorArm64") + + + add("Java") + add("jvm") + + add("JavaScript") + add("jsLegacy") + add("jslr") + + add("Linux") + add("LinuxMipsel32") + add("LinuxArm64") + add("LinuxArm32Hfp") + add("LinuxX64") + + add("Windows") + add("WindowsX64") + add("WindowsX86") + + add("WebAssembly") + add("wasm") + add("wasm32") + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt index 0a9f4a30..4cc06021 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt @@ -65,6 +65,14 @@ fun PackageSearchInfoPanel( content = activeTab ) } + + is InfoPanelContent.Attributes.FromVariant -> { + HeaderAttributesTab(content = activeTab, scrollState = viewModel.scrollState) + + } + is InfoPanelContent.Attributes.FromSearch -> { + HeaderAttributesTab(content = activeTab, scrollState = viewModel.scrollState) + } } } VerticalScrollbar( diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/tree/PackageSearchModulesTree.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/tree/PackageSearchModulesTree.kt index 71e3e739..4f98fd43 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/tree/PackageSearchModulesTree.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/tree/PackageSearchModulesTree.kt @@ -30,10 +30,7 @@ import com.jetbrains.packagesearch.plugin.ui.model.tree.TreeItemModel import com.jetbrains.packagesearch.plugin.ui.model.tree.TreeViewModel import com.jetbrains.packagesearch.plugin.ui.viewModel import org.jetbrains.jewel.foundation.lazy.tree.Tree -import org.jetbrains.jewel.foundation.lazy.tree.buildTree -import org.jetbrains.jewel.foundation.lazy.tree.rememberTreeState import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.foundation.theme.ThemeDefinition import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Icon @@ -157,7 +154,7 @@ private fun TreeActionToolbar( modifier = Modifier.padding(5.dp), resource = "icons/intui/toggleOfflineMode.svg", iconClass = IconProvider::class.java, - contentDescription = "Package Search is offline." + contentDescription = "Package FromSearch is offline." ) }, ) diff --git a/plugin/src/main/resources/messages/packageSearchBundle.properties b/plugin/src/main/resources/messages/packageSearchBundle.properties index 96e92e95..56b0bdf9 100644 --- a/plugin/src/main/resources/messages/packageSearchBundle.properties +++ b/plugin/src/main/resources/messages/packageSearchBundle.properties @@ -215,6 +215,7 @@ packagesearch.toolwindow.loading.easterEgg.2=Asking the Mages in Dalaran to Tele packagesearch.toolwindow.loading.easterEgg.3=Hiring Goblin Engineers for High-Speed Package Data Transfer... packagesearch.toolwindow.loading.easterEgg.4=Deciphering Runes in Ulduar for Encrypted Package Data... packagesearch.ui.toolwindow.packages.details.info.overview=Overview +packagesearch.ui.toolwindow.packages.details.info.attributes=Attributes packagesearch.ui.toolwindow.packages.details.info.unknown=Unknown packagesearch.ui.tree.tooltips.offline=Package Search is offline packagesearch.ui.toolwindow.packages.details.info.scm.github=GitHub @@ -222,4 +223,8 @@ packagesearch.ui.toolwindow.packages.actions.remove=Remove packagesearch.ui.toolwindow.packages.details.info.scope=Scope: packagesearch.toolwindow.loading.dumbMode=Project is initializing... packagesearch.ui.tree.tooltips.expand=Expand all -packagesearch.ui.tree.tooltips.collapse=Collapse all \ No newline at end of file +packagesearch.ui.tree.tooltips.collapse=Collapse all +packagesearch.ui.toolwindow.sidepanel.platformsList=Platforms: +packagesearch.ui.toolwindow.sidepanel.platforms=Platforms +packagesearch.ui.toolwindow.sidepanel.searchResultSupport=Search results that support: +packagesearch.ui.toolwindow.sidepanel.searchResultSupport.sourceSets=Source Sets: