[libbluray-devel] [Git][videolan/libbluray][master] BD-J: Implement HGraphicLook for HStaticIcon/HIcon/HGraphicButton

Petri Hintukainen (@hpi) gitlab at videolan.org
Sun Jun 7 13:15:52 UTC 2026



Petri Hintukainen pushed to branch master at VideoLAN / libbluray


Commits:
9902c487 by spotter at 2026-06-07T13:15:45+00:00
BD-J: Implement HGraphicLook for HStaticIcon/HIcon/HGraphicButton

- - - - -


1 changed file:

- src/libbluray/bdj/java/org/havi/ui/HGraphicLook.java


Changes:

=====================================
src/libbluray/bdj/java/org/havi/ui/HGraphicLook.java
=====================================
@@ -1,6 +1,7 @@
 /*
  * This file is part of libbluray
  * Copyright (C) 2010  William Hahne
+ * Copyright (C) 2026  libbluray project
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -15,69 +16,341 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library. If not, see
  * <http://www.gnu.org/licenses/>.
+ *
+ * Rendering implementation based on XletView by Martin Sveden.
  */
 
 package org.havi.ui;
 
+import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Graphics;
+import java.awt.Image;
 import java.awt.Insets;
 
 public class HGraphicLook implements HExtendedLook {
+
+    private static final Insets DEFAULT_INSETS = new Insets(2, 2, 2, 2);
+    private static final Insets NO_INSETS = new Insets(0, 0, 0, 0);
+
     public HGraphicLook() {
     }
 
     public void fillBackground(Graphics g, HVisible visible, int state) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "fillBackground");
+        if (visible.getBackgroundMode() == HVisible.BACKGROUND_FILL) {
+            Color bg = visible.getBackground();
+            if (bg != null) {
+                Dimension size = visible.getSize();
+                Insets insets = getInsets(visible);
+                g.setColor(bg);
+                // Fill only the content area, not the border/inset area
+                g.fillRect(insets.left, insets.top,
+                    size.width - insets.left - insets.right,
+                    size.height - insets.top - insets.bottom);
+            }
+        }
     }
 
     public void renderBorders(Graphics g, HVisible visible, int state) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "renderBorders");
+        if (!visible.getBordersEnabled()) {
+            return;
+        }
+
+        // Draw focus border when component is focused
+        if ((state & HState.FOCUSED_STATE_BIT) != 0) {
+            Color fg = visible.getForeground();
+            if (fg != null) {
+                Dimension size = visible.getSize();
+                Insets insets = getInsets(visible);
+                g.setColor(fg);
+
+                // Top border
+                g.fillRect(0, 0, size.width, insets.top);
+
+                // Right border
+                g.fillRect(size.width - insets.right, 0, insets.right, size.height);
+
+                // Bottom border
+                g.fillRect(0, size.height - insets.bottom, size.width, insets.bottom);
+
+                // Left border
+                g.fillRect(0, 0, insets.left, size.height);
+            }
+        }
     }
 
     public void renderVisible(Graphics g, HVisible visible, int state) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "renderVisible");
+        // Get the image for this state
+        Image imageToDraw = visible.getGraphicContent(state);
+        if (imageToDraw != null) {
+            Insets insets = getInsets(visible);
+            drawImage(g, imageToDraw, visible, insets);
+        }
     }
 
+    /**
+     * Main rendering method that draws the HVisible according to the HAVI spec.
+     * Order of operations:
+     * 1. Fill background (if BACKGROUND_FILL mode)
+     * 2. Render state-based content
+     * 3. Render borders (if focused and borders enabled)
+     */
     public void showLook(Graphics g, HVisible visible, int state) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "showLook");
-    }
+        // 1. Fill background
+        fillBackground(g, visible, state);
 
-    public void widgetChanged (HVisible visible, HChangeData[] changes) {
-        if (visible.isVisible()) visible.repaint();
-    }
+        // 2. Render the graphic content for this state
+        renderVisible(g, visible, state);
 
-    public Dimension getMinimumSize(HVisible hvisible) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "getMinimumSize");
-        return null;
+        // 3. Render focus borders
+        renderBorders(g, visible, state);
     }
 
-    public Dimension getPreferredSize(HVisible hvisible) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "getPreferredSize");
-        return null;
+    /**
+     * Draws an image onto the HVisible with proper alignment.
+     * Uses the HVisible's horizontal and vertical alignment settings.
+     * Renders within the content area (excluding insets/borders).
+     *
+     * Note: This implementation only supports RESIZE_NONE mode as per HAVI spec
+     * minimum requirements. Scaling support is optional.
+     *
+     * @param g Graphics context
+     * @param imageToDraw The image to draw
+     * @param visible The HVisible owner
+     * @param insets The insets defining the border area
+     * @return true if the image was drawn successfully
+     */
+    protected static boolean drawImage(Graphics g, Image imageToDraw, HVisible visible, Insets insets) {
+        int hAlign = visible.getHorizontalAlignment();
+        int vAlign = visible.getVerticalAlignment();
+
+        // Calculate content area (excluding insets)
+        // Using contentX/contentY for consistency with contentWidth/contentHeight
+        int contentX = insets.left;
+        int contentY = insets.top;
+        int contentWidth = visible.getWidth() - insets.left - insets.right;
+        int contentHeight = visible.getHeight() - insets.top - insets.bottom;
+
+        if (contentWidth <= 0 || contentHeight <= 0) {
+            return false;
+        }
+
+        int imgWidth = imageToDraw.getWidth(visible);
+        int imgHeight = imageToDraw.getHeight(visible);
+
+        // If image dimensions aren't available yet, use content area size
+        if (imgWidth <= 0) imgWidth = contentWidth;
+        if (imgHeight <= 0) imgHeight = contentHeight;
+
+        /*
+         * Per HAVI spec: "Note that the results of applying the VALIGN_JUSTIFY
+         * and HALIGN_JUSTIFY alignment modes for graphical content are defined
+         * to be identical to VALIGN_CENTER and HALIGN_CENTER modes respectively,
+         * as justification is meaningless in this context."
+         */
+
+        int drawX = contentX;
+        int drawY = contentY;
+
+        // Calculate horizontal position within content area
+        switch (hAlign) {
+            case HVisible.HALIGN_CENTER:
+            case HVisible.HALIGN_JUSTIFY:
+                drawX = contentX + (contentWidth - imgWidth) / 2;
+                break;
+            case HVisible.HALIGN_LEFT:
+                drawX = contentX;
+                break;
+            case HVisible.HALIGN_RIGHT:
+                drawX = contentX + contentWidth - imgWidth;
+                break;
+        }
+
+        // Calculate vertical position within content area
+        switch (vAlign) {
+            case HVisible.VALIGN_CENTER:
+            case HVisible.VALIGN_JUSTIFY:
+                drawY = contentY + (contentHeight - imgHeight) / 2;
+                break;
+            case HVisible.VALIGN_TOP:
+                drawY = contentY;
+                break;
+            case HVisible.VALIGN_BOTTOM:
+                drawY = contentY + contentHeight - imgHeight;
+                break;
+        }
+
+        return g.drawImage(imageToDraw, drawX, drawY, visible);
     }
 
-    public Dimension getMaximumSize(HVisible hvisible) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "getMaximumSize");
-        return null;
+    public void widgetChanged(HVisible visible, HChangeData[] changes) {
+        /*
+         * Per HAVI spec: "Note that implementations of HLook may not actually
+         * implement more efficient drawing code for a given hint. In particular,
+         * simply repainting the entire HVisible is a valid implementation option."
+         *
+         * "A minimum implementation of this method could simply call visible.repaint()"
+         */
+        if (visible.isVisible()) {
+            visible.repaint();
+        }
     }
 
-    public boolean isOpaque(HVisible visible) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "isOpaque");
-        return false;
+    /**
+     * Gets the maximum content size across all states.
+     * Returns the widest width and tallest height to fit all content without scaling.
+     *
+     * Note: This implementation only supports RESIZE_NONE (no scaling).
+     *
+     * @param visible The HVisible to examine
+     * @return Content dimensions, or null if no content with valid dimensions
+     */
+    private Dimension getContentSize(HVisible visible) {
+        int maxWidth = 0;
+        int maxHeight = 0;
+        boolean foundContent = false;
+
+        for (int state = HState.FIRST_STATE; state <= HState.LAST_STATE; state++) {
+            Image img = visible.getGraphicContent(state);
+            if (img != null) {
+                int w = img.getWidth(visible);
+                int h = img.getHeight(visible);
+                if (w > 0 && h > 0) {
+                    foundContent = true;
+                    if (w > maxWidth) maxWidth = w;
+                    if (h > maxHeight) maxHeight = h;
+                }
+            }
+        }
+
+        if (!foundContent) {
+            return null;
+        }
+        return new Dimension(maxWidth, maxHeight);
     }
 
-    public Insets getInsets(HVisible hvisible) {
-        org.videolan.Logger.unimplemented(HGraphicLook.class.getName(), "getInsets");
-        return new Insets(0, 0, 0, 0);
+    public Dimension getMinimumSize(HVisible visible) {
+        Insets insets = getInsets(visible);
+        int insetsWidth = insets.left + insets.right;
+        int insetsHeight = insets.top + insets.bottom;
+
+        // Step 1: HTextLook-specific - skip for HGraphicLook
+        // Step 2: Scaling - skip (this implementation only supports RESIZE_NONE)
+
+        // Step 3: No scaling, content set - return size large enough for all content
+        Dimension contentSize = getContentSize(visible);
+        if (contentSize != null) {
+            return new Dimension(contentSize.width + insetsWidth,
+                               contentSize.height + insetsHeight);
+        }
+
+        // Step 4: No content but default size set
+        Dimension defaultSize = visible.getDefaultSize();
+        if (defaultSize != null &&
+            defaultSize.width != HVisible.NO_DEFAULT_WIDTH &&
+            defaultSize.height != HVisible.NO_DEFAULT_HEIGHT) {
+            return new Dimension(defaultSize.width + insetsWidth,
+                               defaultSize.height + insetsHeight);
+        }
+
+        // Step 5: No content or default size - implementation-specific minimum
+        return new Dimension(insetsWidth, insetsHeight);
     }
-}
 
+    public Dimension getPreferredSize(HVisible visible) {
+        Insets insets = getInsets(visible);
+        int insetsWidth = insets.left + insets.right;
+        int insetsHeight = insets.top + insets.bottom;
+
+        // Step 1: If default size set, return it + insets
+        Dimension defaultSize = visible.getDefaultSize();
+        if (defaultSize != null) {
+            int w = defaultSize.width;
+            int h = defaultSize.height;
+
+            boolean hasDefaultWidth = (w != HVisible.NO_DEFAULT_WIDTH);
+            boolean hasDefaultHeight = (h != HVisible.NO_DEFAULT_HEIGHT);
+
+            if (hasDefaultWidth && hasDefaultHeight) {
+                // Full default size is set
+                return new Dimension(w + insetsWidth, h + insetsHeight);
+            }
 
+            // Handle NO_DEFAULT_WIDTH or NO_DEFAULT_HEIGHT cases
+            if (hasDefaultWidth || hasDefaultHeight) {
+                Dimension contentSize = getContentSize(visible);
+                if (contentSize != null) {
+                    if (!hasDefaultWidth) {
+                        w = contentSize.width;
+                    }
+                    if (!hasDefaultHeight) {
+                        h = contentSize.height;
+                    }
+                    return new Dimension(w + insetsWidth, h + insetsHeight);
+                }
+                // No content - use current size for missing dimension
+                Dimension currentSize = visible.getSize();
+                if (!hasDefaultWidth) {
+                    w = Math.max(0, currentSize.width - insetsWidth);
+                }
+                if (!hasDefaultHeight) {
+                    h = Math.max(0, currentSize.height - insetsHeight);
+                }
+                return new Dimension(w + insetsWidth, h + insetsHeight);
+            }
+        }
 
+        // Step 2: HTextLook-specific - skip for HGraphicLook
 
+        // Step 3: No scaling, content present - return size large enough for content
+        // (Step 4 scaling case skipped - this implementation only supports RESIZE_NONE)
+        Dimension contentSize = getContentSize(visible);
+        if (contentSize != null) {
+            return new Dimension(contentSize.width + insetsWidth,
+                               contentSize.height + insetsHeight);
+        }
 
+        // Step 5: No content and no default size - return current size
+        return visible.getSize();
+    }
+
+    public Dimension getMaximumSize(HVisible visible) {
+        Insets insets = getInsets(visible);
+        int insetsWidth = insets.left + insets.right;
+        int insetsHeight = insets.top + insets.bottom;
+
+        // Step 1: HTextLook-specific - skip for HGraphicLook
+        // Step 2: Scaling - skip (this implementation only supports RESIZE_NONE)
+
+        // Step 3: No scaling, content set - return size large enough for content
+        Dimension contentSize = getContentSize(visible);
+        if (contentSize != null) {
+            return new Dimension(contentSize.width + insetsWidth,
+                               contentSize.height + insetsHeight);
+        }
+
+        // Step 4: No content - return Short.MAX_VALUE
+        return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
+    }
 
+    public boolean isOpaque(HVisible visible) {
+        // Component is opaque if background fill is enabled and has opaque background color
+        if (visible.getBackgroundMode() != HVisible.BACKGROUND_FILL) {
+            return false;
+        }
 
+        Color bg = visible.getBackground();
+        if ((bg == null) || (bg.getAlpha() < 255)) {
+            return false;
+        }
 
+        return true;
+    }
 
+    public Insets getInsets(HVisible visible) {
+        if (!visible.getBordersEnabled()) {
+            return NO_INSETS;
+        }
+        return DEFAULT_INSETS;
+    }
+}



View it on GitLab: https://code.videolan.org/videolan/libbluray/-/commit/9902c487d8fa7c80c3ad04b49c00cb8f3ec2b95d

-- 
View it on GitLab: https://code.videolan.org/videolan/libbluray/-/commit/9902c487d8fa7c80c3ad04b49c00cb8f3ec2b95d
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the libbluray-devel mailing list