Слои Swing - прозрачный компонент игнорирует базовый элемент AWT

Во-первых, чтобы избавиться от этого, мне абсолютно НЕОБХОДИМО использовать тяжеловесный компонент AWT с приложением Swing. Мне нужны функции от обоих из них.

Задача проста — визуализировать тяжеловесный холст AWT (или любой другой элемент), визуализировать сцену OpenGL непосредственно на нем, а затем отобразить над ним кнопки Swing для пользовательского интерфейса.

Моя проблема в том, что он работает наполовину. Кажется, у меня нет проблем с Z-упорядочением. Я использую для этого jLayeredPanes, и я могу перемещать Canvas между слоями, и это действительно работает, выскакивая поверх или под другие элементы.

Проблемы с прозрачностью. Дело в том, что элементы Swing имеют параметр Opaque, и когда он установлен в false (непрозрачный) - он должен быть прозрачным, и вы должны видеть следующий элемент под ним. Однако в моем случае холст AWT игнорируется, и вместо этого вы видите только следующий нижележащий элемент SWING.

Вот пара скриншотов. Они взяты из моего отдельного тестового проекта. Холст растягивается до размера кадра, а в левом верхнем углу есть фиктивный элемент JLayeredPane, представляющий собой упрощенную версию меню.

На первом снимке экрана для параметра Opaque JLayeredPane установлено значение true, и вы можете видеть, что его свойство фона имеет синий цвет.

На втором снимке экрана все точно так же, но для Opaque установлено значение false. Вместо того, чтобы отображать то, что находится на холсте, — то, что рисуется на пустом сером фоне jFrame.

Наконец, на третьем снимке экрана я поместил Canvas в jPanel вместо того, чтобы оставить его отдельно. Как видите, оранжевый цвет панели виден сквозь прозрачную панель jLayeredPane, но холст снова скрыт.

paneMenu Opaque = true paneMenu.setOpaque(false) paneMenu.setOpaque(false) + Canvas внутри jPanel

Вот код макета Frame. Я бы не стал публиковать код рендеринга/контекста прямо сейчас

frame = new JFrame("AWT test");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setLayout(new BorderLayout());
    frame.setPreferredSize(new Dimension(width, height));

    canvas = new Canvas();
    canvas.setSize(width,height);

    //this part exists only in the third example
    JPanel p = new JPanel();
    p.setSize(width,height);
    p.setBackground(Color.orange);
    p.add(canvas);
    // third example end

    JLayeredPane pane = new JLayeredPane();
    JLayeredPane paneMenu = new JLayeredPane();
    JButton button = new JButton();
    button.setSize(20,20);
    paneMenu.setSize(200,200);
    paneMenu.add(button, new Integer(1));
    paneMenu.setBackground(Color.BLUE);
    paneMenu.setOpaque(false);           //True for the first example

    pane.add(p, new Integer(1));    // canvas for the first two examples
    pane.add(paneMenu, new Integer(2));

    pane.setOpaque(false);

    frame.add(pane);
    frame.pack();
    frame.setVisible(true);
    frame.transferFocus();

Может ли кто-нибудь объяснить мне, что происходит и как сделать то, что мне нужно сделать. Я повторюсь еще раз - я должен использовать тяжеловесный компонент в качестве цели рендеринга. Я знаю о таких решениях, как GLPanel JOGL, который является легким Swing-совместимым компонентом. Но я попробовал этот метод, и производительность действительно низкая, потому что вместо прямого рендеринга на него как на цель контекста он считывает FrameBuffer из памяти, переворачивает его, а затем рисует как BufferedImage. Этот путь не подходит для ограниченных ресурсов встроенной системы, на которой я буду работать.

c0der сказал: "Пожалуйста, опубликуйте минимальный воспроизводимый пример Ээээ.... Не так ли? Здесь вы можете получить его в полной форме класса Java, если хотите, но я буквально заменил некоторые переменные на константы.

import javax.swing.*;
import java.awt.*;

public class Main
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame("AWT test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.setPreferredSize(new Dimension(500, 500));

        Canvas canvas = new Canvas();
        canvas.setSize(500,500);
        canvas.setBackground(Color.RED);

        //this part exists only in the third example
        JPanel p = new JPanel();
        p.setSize(500,500);
        p.setBackground(Color.orange);
        p.add(canvas);
        // third example end

        JLayeredPane pane = new JLayeredPane();
        JLayeredPane paneMenu = new JLayeredPane();
        JButton button = new JButton();
        button.setSize(20,20);
        paneMenu.setSize(200,200);
        paneMenu.add(button, new Integer(1));
        paneMenu.setBackground(Color.BLUE);
        paneMenu.setOpaque(false);           //True for the first example

        pane.add(p, new Integer(1));    // canvas for the first two examples
        pane.add(paneMenu, new Integer(2));

        pane.setOpaque(false);

        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
        frame.transferFocus();
    }
}

Небольшое обновление:

Сначала я подозревал, что, поскольку элементы Swing делегируют все свои отрисовки базовому тяжеловесному элементу (в моем случае JFrame), происходит то, что фрейм генерирует для себя один фреймбуфер, а затем отображается поверх Canvas. Сам холст в этом поколении не обрабатывается, поэтому рамка «закрывает» холст.

Кажется, это не так. Я попытался сделать JFrame неукрашенным, все панели непрозрачными и отобразить изображение. Результат - холст все-таки "разрезан", и через дырку видно нижележащее меню IDE. Это заставляет меня думать, что где-то во время рисования Canvas сам определяет, что он закрыт другим элементом, и что ему не нужно рисовать эту область. Таким образом, он «оптимизирует» себя и не обновляет эти пиксели.

Может быть, я ошибаюсь. Но вот еще скриншот. Это тот же пример, что и раньше, но я убрал 3D-рендеринг и просто попытался отобразить Canvas с фоном, установленным на красный.

введите здесь описание изображения


person Vlad Kozmyuk    schedule 13.12.2019    source источник
comment
Опубликуйте минимально воспроизводимый пример   -  person c0der    schedule 14.12.2019
comment
хорошо.... Но код, который я разместил, был в основном минимальным воспроизводимым примером, не так ли?   -  person Vlad Kozmyuk    schedule 14.12.2019
comment
Теперь есть. Почему paneMenu это JLayeredPane, а не JPanel?   -  person c0der    schedule 14.12.2019
comment
Потому что в конечном приложении структура пользовательского интерфейса немного сложна. У них есть основной jLayeredPane, внутри которого у них есть элементы GUI на разных слоях. И каждый такой элемент — это еще одна LayeredPane, в которой кнопки и ярлыки значков расположены друг над другом. Но на самом деле это не имеет значения, потому что ошибка проявляется даже без другой LayeredPane. Просто, если вы поместите JPanel в основную панель и сделаете ее непрозрачной = false, она будет отображать то же поведение.   -  person Vlad Kozmyuk    schedule 14.12.2019


Ответы (1)


Еще раз, собираюсь ответить на мой собственный вопрос.

Оказалось, что мне нужно сделать

setComponentMixingCutoutShape (paneMenu, новый прямоугольник ());

для панели меню, которая находится под кнопкой. По сути, это говорит java не вырезать элемент из тяжеловесного базового компонента.

person Vlad Kozmyuk    schedule 17.12.2019