Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip is hidden behind JPanel #4345

Closed
zoff99 opened this issue Feb 22, 2024 · 4 comments
Closed

Tooltip is hidden behind JPanel #4345

zoff99 opened this issue Feb 22, 2024 · 4 comments
Labels
bug Something isn't working desktop duplicate This issue or pull request already exists

Comments

@zoff99
Copy link

zoff99 commented Feb 22, 2024

Describe the bug
when showing a tooltip on mouseover the tooltip gets hidden behind a JPanel.

Affected platforms
Select one of the platforms below:

  • Desktop

Versions

  • Kotlin version*: 1.9.22
  • Compose Multiplatform version*: 1.5.x and 1.6.x
  • OS version(s)* (required for Desktop and iOS issues): linux
  • OS architecture (x86 or arm64): x86
  • JDK (for desktop issues): 17

Expected behavior
tooltip should show above JPanel

Screenshots
tooltip_behind_jpanel

@zoff99 zoff99 added bug Something isn't working submitted labels Feb 22, 2024
@zoff99
Copy link
Author

zoff99 commented Feb 22, 2024


@Composable
@OptIn(ExperimentalFoundationApi::class)
fun Tooltip(
    text: String?,
    modifier: Modifier = Modifier,
    delayMillis: Int = 500,
    tooltipPlacement: TooltipPlacement = TooltipPlacement.CursorPoint(
        offset = DpOffset(0.dp, 16.dp)
    ),
    content: @Composable () -> Unit,
) {
    if (text.isNullOrEmpty()) {
        Box(modifier = modifier) {
            content()
        }
    } else {
        TooltipArea(
            tooltip = {
                Surface(
                    modifier = Modifier.shadow(4.dp),
                    shape = RoundedCornerShape(4.dp),
                ) {
                    Text(
                        text = text,
                        modifier = Modifier.padding(4.dp),
                    )
                }
            },
            delayMillis = delayMillis,
            tooltipPlacement = tooltipPlacement,
            modifier = modifier, // todo: use tooltip text for semantic description
            content = content,
        )
    }
}



class SingleComponentAspectRatioKeeperLayout : LayoutManager
{
    init
    {
        fakeComponent.preferredSize = Dimension(0, 0)
    }

    override fun addLayoutComponent(s: String, component: Component)
    {
    }

    override fun removeLayoutComponent(component: Component)
    {
    }

    override fun preferredLayoutSize(parent: Container): Dimension
    {
        return getSingleComponent(parent).preferredSize
    }

    override fun minimumLayoutSize(parent: Container): Dimension
    {
        return preferredLayoutSize(parent)
    }

    override fun layoutContainer(parent: Container)
    {
        val component = getSingleComponent(parent)
        val insets = parent.insets
        val maxWidth = parent.width - (insets.left + insets.right)
        val maxHeight = parent.height - (insets.top + insets.bottom)
        val prefferedSize = component.preferredSize
        val targetDim = getScaledDimension(prefferedSize, Dimension(maxWidth, maxHeight))
        val targetWidth = targetDim.getWidth()
        val targetHeight = targetDim.getHeight()
        val hgap = (maxWidth - targetWidth) / 2
        val vgap = (maxHeight - targetHeight) / 2 // Set the single component's size and position.
        component.setBounds(hgap.toInt(), vgap.toInt(), targetWidth.toInt(), targetHeight.toInt())
    }

    private fun getSingleComponent(parent: Container): Component
    {
        val parentComponentCount = parent.componentCount
        require(parentComponentCount <= 1) {
            this.javaClass.simpleName + " can not handle more than one component"
        }
        val comp = if ((parentComponentCount == 1)) parent.getComponent(0) else fakeComponent
        return comp
    }

    private fun getScaledDimension(imageSize: Dimension, boundary: Dimension): Dimension
    {
        val widthRatio = boundary.getWidth() / imageSize.getWidth()
        val heightRatio = boundary.getHeight() / imageSize.getHeight()
        val ratio = min(widthRatio, heightRatio) // Log.i(TAG, "w=" + imageSize.width + " h=" + imageSize.height + " r=" + ratio);
        return Dimension((imageSize.width * ratio).toInt(), (imageSize.height * ratio).toInt())
    }

    companion object
    {
        private const val TAG = "trifa.tAspectRatio"
        private val fakeComponent: Component = JPanel(true)
    }
}

val videoinbox = JPictureBox()
class JPictureBox : JComponent()
{
    private val dimension = Dimension(100, 100)
    fun JPictureBox()
    {
        preferredSize = dimension
        isOpaque = false
        isDoubleBuffered = true
        ignoreRepaint = true
    }
}

val VIDEO_IN_BOX_WIDTH_SMALL = 80.dp
val VIDEO_IN_BOX_HEIGHT_SMALL = 80.dp
const val VIDEO_IN_BOX_WIDTH_FRACTION_SMALL = 0.3f
const val VIDEO_IN_BOX_WIDTH_FRACTION_BIG = 0.9f
val VIDEO_IN_BOX_WIDTH_BIG = 800.dp
val VIDEO_IN_BOX_HEIGHT_BIG = 3000.dp
val VIDEO_OUT_BOX_WIDTH_SMALL = 130.dp
val VIDEO_OUT_BOX_HEIGHT_SMALL = 100.dp
val VIDEO_OUT_BOX_WIDTH_BIG = 500.dp
val VIDEO_OUT_BOX_HEIGHT_BIG = 500.dp

@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview
fun App()
{
    Row(Modifier.fillMaxSize().background(Color.White)) {
        Tooltip(text = "long tooltip long tooltip long tooltip long tooltip long tooltip long tooltip long tooltip") {
            Text(text = "aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\n")
        }
        Column(modifier = Modifier.fillMaxHeight(1.0f)) {
            var video_in_box_width by remember { mutableStateOf(VIDEO_IN_BOX_WIDTH_SMALL) }
            var video_in_box_height by remember { mutableStateOf(VIDEO_IN_BOX_HEIGHT_SMALL) }
            var video_in_box_small by remember { mutableStateOf(true) }
            var video_in_box_width_fraction by remember { mutableStateOf(VIDEO_IN_BOX_WIDTH_FRACTION_SMALL) }

            SwingPanel(background = Color.Green, modifier = Modifier.fillMaxWidth(video_in_box_width_fraction).padding(5.dp).weight(80.0f).combinedClickable(onClick = {
                    if (video_in_box_small)
                    {
                        video_in_box_width = VIDEO_IN_BOX_WIDTH_BIG
                        video_in_box_height = VIDEO_IN_BOX_HEIGHT_BIG
                    } else
                    {
                        video_in_box_width = VIDEO_IN_BOX_WIDTH_SMALL
                        video_in_box_height = VIDEO_IN_BOX_HEIGHT_SMALL
                    }
                    video_in_box_small != video_in_box_small
                }), factory = {
                JPanel(SingleComponentAspectRatioKeeperLayout(), true).apply {
                    add(videoinbox)
                }
            })
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

@dima-avdeev-jb
Copy link
Contributor

Hello! Thanks for Issue.
Currently this is Compose for Desktop limitation.

But there are more common Issue here: #2926
You can follow on it.

@MatkovIvan
Copy link
Member

Duplicate of #2926 (as well as #3353, #3474, #3739, #3823)

For 1.5.x and below it was documented limitation:

The second use case is situation when you want to use some component, that exists in Swing and there is no analogue in Compose. And creating it from scratch is too expensive. In this case, you can use `SwingPanel`. A `SwingPanel` is a wrapper that controls the size, position and rendering of a Swing component that is placed on top of a Compose Multiplatform component, meaning the component inside a `SwingPanel` will always be on top of the Compose in depth. Anything that is misplaced and rests on the `SwingPanel` will be clipped by the Swing component placed there, so try to think about these situations, and if there is such a risk, then it is better to either redesign the UI accordingly, or stop using the `SwingPanel` and still try to implement the missing component, thereby contributing to the development of technology and making life easier for other developers.

For upcoming 1.6.x it should be mitigated by experimental blending option: JetBrains/compose-multiplatform-core#915

@MatkovIvan MatkovIvan added the duplicate This issue or pull request already exists label Feb 22, 2024
@okushnikov
Copy link

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working desktop duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

4 participants