From 3c8825879b1491e120eec355cdb938fd28ec0042 Mon Sep 17 00:00:00 2001 From: Gary Tierney Date: Sat, 13 Apr 2024 14:39:15 +0100 Subject: [PATCH] Support QuickSearch previews --- src/main/kotlin/ui/root/WorkspaceView.kt | 30 +++++++--- src/main/kotlin/ui/search/QuickSearch.kt | 57 ++++++++++++++++--- ...ickSearchWindow.kt => QuickSearchPopup.kt} | 5 +- 3 files changed, 72 insertions(+), 20 deletions(-) rename src/main/kotlin/ui/search/{QuickSearchWindow.kt => QuickSearchPopup.kt} (92%) diff --git a/src/main/kotlin/ui/root/WorkspaceView.kt b/src/main/kotlin/ui/root/WorkspaceView.kt index 39993c6..018b590 100644 --- a/src/main/kotlin/ui/root/WorkspaceView.kt +++ b/src/main/kotlin/ui/root/WorkspaceView.kt @@ -2,26 +2,23 @@ package io.github.garyttierney.ghidralite.ui.root import androidx.compose.foundation.background import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.SwingPanel import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.* -import androidx.compose.ui.unit.dp import io.github.garyttierney.ghidralite.framework.search.SearchResult import io.github.garyttierney.ghidralite.ui.search.QuickSearch -import io.github.garyttierney.ghidralite.ui.search.QuickSearchWindow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi -import org.jetbrains.compose.splitpane.HorizontalSplitPane import org.jetbrains.compose.splitpane.rememberSplitPaneState import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.component.* +import java.awt.BorderLayout +import javax.swing.JPanel @OptIn(ExperimentalSplitPaneApi::class) @@ -61,6 +58,23 @@ fun WorkspaceView(model: Workspace) = key(model) { QuickSearch( items = searchResults, query = searchQuery, + itemPreview = { item -> + Box(modifier = Modifier.background(Color.Red)) { + SwingPanel( + modifier = Modifier.fillMaxSize().background(Color.Red), + factory = { + + val panel = JPanel(BorderLayout()) + panel.add(model.listing, BorderLayout.CENTER) + panel + }, + update = { + val sym = model.program.symbolTable.getSymbol(item.element.key as Long) + model.listing.goTo(sym.address) + } + ) + } + }, onQueryChanged = { searchQuery = it searchResults.clear() diff --git a/src/main/kotlin/ui/search/QuickSearch.kt b/src/main/kotlin/ui/search/QuickSearch.kt index e3d424e..e862a27 100644 --- a/src/main/kotlin/ui/search/QuickSearch.kt +++ b/src/main/kotlin/ui/search/QuickSearch.kt @@ -8,11 +8,13 @@ import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -20,6 +22,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.MenuBar +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberWindowState +import io.github.garyttierney.ghidralite.LocalWindowPosition import io.github.garyttierney.ghidralite.framework.search.SearchResult import kotlinx.coroutines.launch import org.jetbrains.jewel.foundation.lazy.* @@ -31,6 +38,7 @@ import org.jetbrains.jewel.ui.component.* import org.jetbrains.jewel.ui.theme.colorPalette import org.jetbrains.jewel.ui.theme.menuStyle +@OptIn(ExperimentalComposeUiApi::class) @Composable fun QuickSearch( items: List, @@ -38,10 +46,12 @@ fun QuickSearch( onQueryChanged: (String) -> Unit, onResultSelected: (SearchResult) -> Unit, focusRequester: FocusRequester = FocusRequester(), - listActions: KeyActions = remember { DefaultSelectableLazyColumnKeyActions(DefaultSelectableColumnKeybindings) } + listActions: KeyActions = remember { DefaultSelectableLazyColumnKeyActions(DefaultSelectableColumnKeybindings) }, + itemPreview: @Composable (SearchResult) -> Unit = {} ) { val itemListState = rememberSelectableLazyListState() val itemKeys by derivedStateOf { items.map { SelectableLazyListKey.Selectable(it.element.key) }.toList() } + val itemSelected by derivedStateOf { items.find { it.element.key == itemListState.selectedKeys.firstOrNull() } } val handleListAction = { event: KeyEvent -> val itemListHandler = listActions.handleOnKeyEvent(event, itemKeys, itemListState, SelectionMode.Single) @@ -53,14 +63,44 @@ fun QuickSearch( } } - Column(modifier = Modifier.onPreviewKeyEvent(handleListAction).focusRequester(focusRequester).focusGroup()) { - QuickSearchInput(query = query, onQueryChanged = onQueryChanged) + Row { + Column(modifier = Modifier.onPreviewKeyEvent(handleListAction).focusRequester(focusRequester).focusGroup()) { + QuickSearchInput(query = query, onQueryChanged = onQueryChanged) - QuickSearchResultList( - items = items, - state = itemListState, - onItemSelected = onResultSelected, - ) + QuickSearchResultList( + items = items, + itemPreview = itemPreview, + state = itemListState, + onItemSelected = onResultSelected, + ) + } + + itemSelected?.let { + val containerSize = LocalWindowInfo.current.containerSize + val containerPos = LocalWindowPosition.current + val popupOffset = containerSize.width + 10 + val popupPos = WindowPosition.Absolute(containerPos.x + popupOffset.dp, containerPos.y) + val popupState = rememberWindowState( + position = popupPos, + width = containerSize.width.dp, + height = containerSize.height.dp + ) + + Window( + visible = true, + onCloseRequest = {}, + state = popupState, + undecorated = true, + alwaysOnTop = true, + focusable = false, + ) { + // TODO HACK: A window with a single SwingPanel it doesn't render without the presence of another heavyweight + // component. + MenuBar {} + + itemPreview(it) + } + } } } @@ -84,6 +124,7 @@ fun QuickSearchResultList( items: List, onItemSelected: (SearchResult) -> Unit, state: SelectableLazyListState = rememberSelectableLazyListState(), + itemPreview: @Composable (SearchResult) -> Unit, ) { val listTheme = Modifier.fillMaxSize().background(JewelTheme.menuStyle.colors.background) val scope = rememberCoroutineScope() diff --git a/src/main/kotlin/ui/search/QuickSearchWindow.kt b/src/main/kotlin/ui/search/QuickSearchPopup.kt similarity index 92% rename from src/main/kotlin/ui/search/QuickSearchWindow.kt rename to src/main/kotlin/ui/search/QuickSearchPopup.kt index f6b6fa7..cc7ae3b 100644 --- a/src/main/kotlin/ui/search/QuickSearchWindow.kt +++ b/src/main/kotlin/ui/search/QuickSearchPopup.kt @@ -11,17 +11,14 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition -import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.rememberWindowState import io.github.garyttierney.ghidralite.LocalWindowPosition import io.github.garyttierney.ghidralite.framework.search.SearchResult -import java.util.concurrent.PriorityBlockingQueue @OptIn(ExperimentalComposeUiApi::class) @Composable -fun QuickSearchWindow( +fun QuickSearchPopup( visible: Boolean = true, - state: WindowState = rememberWindowState(), results: List, onResultSelected: (SearchResult) -> Unit, query: String = "",