Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6c61c41
Refine popup bias screenshot test around centered target and fallback
liannacasper Apr 1, 2026
85a2abb
Fix screenshot test style padding API usage
liannacasper Apr 1, 2026
c5f82be
Expand popup bias tests and fix screenshot layout
liannacasper Apr 1, 2026
8814eb4
Add third popup scenario to screenshot bias test
liannacasper Apr 1, 2026
d230b14
Use compact custom UIIDs for popup bias screenshot dialogs
liannacasper Apr 1, 2026
ad1ae38
Fix font style constant in popup bias screenshot test
liannacasper Apr 1, 2026
459e7e3
Use default popup styles in bias screenshot test for visibility
liannacasper Apr 1, 2026
2045e6c
Rework popup bias screenshot to 4 dialogs over 3 targets
liannacasper Apr 1, 2026
b5dc2ff
Make popup bias screenshot dialogs compact to show both arrow directions
liannacasper Apr 1, 2026
095191e
Fix popup arrow orientation padding for above/below placement
liannacasper Apr 1, 2026
d4617b0
Tighten screenshot popup labels and refresh layout before showing
liannacasper Apr 1, 2026
ea20374
Refactor placement booleans and restore border tracking fallback
liannacasper Apr 1, 2026
73d57b5
Use dialog style for popup border/arrow handling
liannacasper Apr 1, 2026
fa2ab11
Restore popup border lookup fallback for arrow rendering
liannacasper Apr 2, 2026
e96f4d0
Fix arrow tracking rect and add deeper border tracking tests
liannacasper Apr 2, 2026
c92cca9
Use absolute track rectangle for popup border arrow direction
liannacasper Apr 2, 2026
500a37c
Stabilize tracking border test by preserving PopupDialog UIID
liannacasper Apr 2, 2026
8e4531d
Add theme-path unit tests for popup arrow image vs tracking fallback
liannacasper Apr 2, 2026
9318141
Fix arrow-theme unit tests to use boolean theme constants
liannacasper Apr 2, 2026
d138f54
Stabilize arrow-path tests and force RoundRect arrow side by placement
liannacasper Apr 2, 2026
becd664
Prefer track-based arrows for RoundRectBorder popups
liannacasper Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 65 additions & 49 deletions CodenameOne/src/com/codename1/components/InteractionDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.RoundRectBorder;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.animations.Transition;
Expand Down Expand Up @@ -637,9 +638,10 @@ public void showPopupDialog(Component c) {
///
/// - `c`: the context component which is used to position the dialog and can also be pointed at
///
/// - `bias`: @param bias biases the dialog to appear above/below or to the sides.
/// This is ignored if there isn't enough space
public void showPopupDialog(Component c, boolean bias) {
/// - `prioritizeTopOrRightPosition`: if `true`, prefer showing above the target (portrait layout) or to
/// the right of the target (landscape layout) when both positions fit. If `false`, prefer below/left.
/// If there isn't enough room in the preferred position, the dialog falls back automatically.
public void showPopupDialog(Component c, boolean prioritizeTopOrRightPosition) {
if (c == null) {
throw new IllegalArgumentException("Component cannot be null");
}
Expand All @@ -653,7 +655,7 @@ public void showPopupDialog(Component c, boolean bias) {
componentPos.setX(componentPos.getX() - c.getScrollX());
componentPos.setY(componentPos.getY() - c.getScrollY());
setOwner(c);
showPopupDialog(componentPos, bias);
showPopupDialog(componentPos, prioritizeTopOrRightPosition);
}

/// A popup dialog is shown with the context of a component and its selection. You should use `#setDisposeWhenPointerOutOfBounds(boolean)` to make it dispose
Expand All @@ -675,9 +677,10 @@ public void showPopupDialog(Rectangle rect) {
///
/// - `rect`: the screen rectangle to which the popup should point
///
/// - `bias`: @param bias biases the dialog to appear above/below or to the sides.
/// This is ignored if there isn't enough space
public void showPopupDialog(Rectangle rect, boolean bias) {
/// - `prioritizeTopOrRightPosition`: if `true`, prefer showing above the target (portrait layout) or to
/// the right of the target (landscape layout) when both positions fit. If `false`, prefer below/left.
/// If there isn't enough room in the preferred position, the dialog falls back automatically.
public void showPopupDialog(Rectangle rect, boolean prioritizeTopOrRightPosition) {
if (rect == null) {
throw new IllegalArgumentException("rect cannot be null");
}
Expand Down Expand Up @@ -719,19 +722,28 @@ public void showPopupDialog(Rectangle rect, boolean bias) {
// allows a text area to recalculate its preferred size if embedded within a dialog
revalidate();

Style contentPaneStyle = getStyle(); // PMD Fix: UnusedLocalVariable removed redundant contentPane reference
// Popup border can be defined either on the dialog style (PopupDialog) or the content pane style
// (PopupContentPane), depending on theme/platform. Prefer dialog style for arrows, and fall back
// to dialog content style if needed.
Style contentPaneStyle = getStyle();
if (contentPaneStyle.getBorder() == null) {
contentPaneStyle = getDialogStyle();
}

if (manager.isThemeConstant(getUIID() + "ArrowBool", false)) {
Image t = manager.getThemeImageConstant(getUIID() + "ArrowTopImage");
Image b = manager.getThemeImageConstant(getUIID() + "ArrowBottomImage");
Image l = manager.getThemeImageConstant(getUIID() + "ArrowLeftImage");
Image r = manager.getThemeImageConstant(getUIID() + "ArrowRightImage");
Border border = contentPaneStyle.getBorder();
boolean arrowEnabled = manager.isThemeConstant(getUIID() + "ArrowBool", false);
Image t = manager.getThemeImageConstant(getUIID() + "ArrowTopImage");
Image b = manager.getThemeImageConstant(getUIID() + "ArrowBottomImage");
Image l = manager.getThemeImageConstant(getUIID() + "ArrowLeftImage");
Image r = manager.getThemeImageConstant(getUIID() + "ArrowRightImage");
boolean hasArrowImages = t != null || b != null || l != null || r != null;
Border border = contentPaneStyle.getBorder();
if (border instanceof RoundRectBorder) {
border.setTrackComponent(origRect);
} else if (arrowEnabled && hasArrowImages) {
if (border != null) {
border.setImageBorderSpecialTile(t, b, l, r, rect);
border.setImageBorderSpecialTile(t, b, l, r, origRect);
}
} else {
Border border = contentPaneStyle.getBorder();
if (border != null) {
border.setTrackComponent(origRect);
}
Expand Down Expand Up @@ -762,7 +774,7 @@ public void showPopupDialog(Rectangle rect, boolean bias) {
int x = 0;
int y = 0;

boolean showPortrait = bias;
boolean showPortrait = Display.getInstance().isPortrait();

// if we don't have enough space then disregard device orientation
if (showPortrait) {
Expand Down Expand Up @@ -790,37 +802,31 @@ public void showPopupDialog(Rectangle rect, boolean bias) {
}
}
}
if (rect.getY() + rect.getHeight() < availableHeight / 2) {
int spaceAbove = rect.getY();
int spaceBelow = availableHeight - (rect.getY() + rect.getHeight());
boolean canShowAbove = prefHeight <= spaceAbove;
boolean canShowBelow = prefHeight <= spaceBelow;
// Boolean decision: honor preference when both sides fit, otherwise use the fitting side,
// and if neither fits use the side with more available space.
boolean showAbove = canShowAbove && (canShowBelow ? prioritizeTopOrRightPosition : true)
|| !canShowAbove && !canShowBelow && spaceAbove >= spaceBelow;

if (!showAbove) {
// popup downwards
y = rect.getY() + rect.getHeight();
int height = Math.min(prefHeight, Math.max(0, availableHeight - y));
padOrientation(contentPaneStyle, TOP, 1);
setTrackedArrowSide(contentPaneStyle.getBorder(), TOP);
padOrientation(contentPaneStyle, BOTTOM, 1);
show(Math.max(0, y), Math.max(0, availableHeight - height - y),
Math.max(0, x), Math.max(0, availableWidth - width - x));
padOrientation(contentPaneStyle, TOP, -1);
} else if (rect.getY() > availableHeight / 2) {
padOrientation(contentPaneStyle, BOTTOM, -1);
} else {
// popup upwards
int height = Math.min(prefHeight, rect.getY());
y = rect.getY() - height;
padOrientation(contentPaneStyle, BOTTOM, 1);
show(y, Math.max(0, availableHeight - rect.getY()), x, Math.max(0, availableWidth - width - x));
padOrientation(contentPaneStyle, BOTTOM, -1);
} else if (rect.getY() < availableHeight / 2) {
// popup over aligned with top of rect, but inset a few mm
y = rect.getY() + CN.convertToPixels(3);

int height = Math.min(prefHeight, availableHeight - y);
padOrientation(contentPaneStyle, BOTTOM, 1);
show(y, Math.max(0, availableHeight - height - y),
Math.max(0, x), Math.max(0, availableWidth - width - x));
padOrientation(contentPaneStyle, BOTTOM, -1);
} else {
// popup over aligned with bottom of rect but inset a few mm
y = Math.max(0, rect.getY() + rect.getHeight() - CN.convertToPixels(3) - prefHeight);
int height = prefHeight;
setTrackedArrowSide(contentPaneStyle.getBorder(), BOTTOM);
padOrientation(contentPaneStyle, TOP, 1);
show(y, Math.max(0, availableHeight - height - y),
Math.max(0, x), Math.max(0, availableWidth - width - x));
show(y, Math.max(0, availableHeight - rect.getY()), x, Math.max(0, availableWidth - width - x));
padOrientation(contentPaneStyle, TOP, -1);
}
} else {
Expand All @@ -839,23 +845,33 @@ public void showPopupDialog(Rectangle rect, boolean bias) {
}
}

if (prefWidth < availableWidth - rect.getX() - rect.getWidth()) {
int spaceRight = availableWidth - rect.getX() - rect.getWidth();
int spaceLeft = rect.getX();
boolean canShowRight = prefWidth <= spaceRight;
boolean canShowLeft = prefWidth <= spaceLeft;
// Boolean decision: honor preference when both sides fit, otherwise use the fitting side,
// and if neither fits use the side with more available space.
boolean showRight = canShowRight && (canShowLeft ? prioritizeTopOrRightPosition : true)
|| !canShowRight && !canShowLeft && spaceRight >= spaceLeft;

if (showRight) {
// popup right
x = rect.getX() + rect.getWidth();


width = Math.min(prefWidth, availableWidth - x);
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
} else if (prefWidth < rect.getX()) {
x = rect.getX() - prefWidth;
width = prefWidth;
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
setTrackedArrowSide(contentPaneStyle.getBorder(), LEFT);
} else {
// popup left
width = Math.min(prefWidth, availableWidth - (availableWidth - rect.getX()));
width = Math.min(prefWidth, rect.getX());
x = rect.getX() - width;
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
setTrackedArrowSide(contentPaneStyle.getBorder(), RIGHT);
}
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
}
}

private void setTrackedArrowSide(Border border, int side) {
if (border instanceof RoundRectBorder) {
((RoundRectBorder) border).trackComponentSide(side);
}
}

Expand Down
Loading
Loading