diff --git a/CodenameOne/src/com/codename1/components/InteractionDialog.java b/CodenameOne/src/com/codename1/components/InteractionDialog.java index d06c410259..8f33cb756a 100644 --- a/CodenameOne/src/com/codename1/components/InteractionDialog.java +++ b/CodenameOne/src/com/codename1/components/InteractionDialog.java @@ -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; @@ -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"); } @@ -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 @@ -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"); } @@ -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); } @@ -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) { @@ -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 { @@ -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); } } diff --git a/maven/core-unittests/src/test/java/com/codename1/components/InteractionDialogTest.java b/maven/core-unittests/src/test/java/com/codename1/components/InteractionDialogTest.java index d72211c9eb..2d7437fb1b 100644 --- a/maven/core-unittests/src/test/java/com/codename1/components/InteractionDialogTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/components/InteractionDialogTest.java @@ -4,14 +4,19 @@ import com.codename1.junit.UITestBase; import com.codename1.ui.Container; import com.codename1.ui.Form; +import com.codename1.ui.Image; import com.codename1.ui.Label; +import com.codename1.ui.layouts.FlowLayout; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.layouts.BorderLayout; +import com.codename1.ui.plaf.Border; +import com.codename1.ui.plaf.UIManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; +import java.util.Hashtable; import static org.junit.jupiter.api.Assertions.*; @@ -97,6 +102,262 @@ void formModeUsesFormLayeredPane() { dialog.dispose(); } + @Test + void showPopupDialogBiasTruePrefersTopWhenBothFit() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(120, 220, 60, 40); + dialog.showPopupDialog(rect, true); + + assertTrue(dialog.getY() + dialog.getHeight() <= rect.getY(), + "Expected popup above target when prioritizeTopOrRightPosition=true"); + dialog.dispose(); + } + + @Test + void showPopupDialogBiasFalsePrefersBottomWhenBothFit() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(120, 220, 60, 40); + dialog.showPopupDialog(rect, false); + + assertTrue(dialog.getY() >= rect.getY() + rect.getHeight(), + "Expected popup below target when prioritizeTopOrRightPosition=false"); + dialog.dispose(); + } + + @Test + void showPopupDialogFallsBackWhenPreferredTopDoesNotFit() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(120, 2, 60, 40); + dialog.showPopupDialog(rect, true); + + assertTrue(dialog.getY() >= rect.getY() + rect.getHeight(), + "Expected fallback below when preferred top side does not fit"); + dialog.dispose(); + } + + @Test + void showPopupDialogFallsBackWhenPreferredBottomDoesNotFit() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + int displayHeight = implementation.getDisplayHeight(); + Rectangle rect = new Rectangle(120, Math.max(0, displayHeight - 8), 60, 6); + dialog.showPopupDialog(rect, false); + + assertTrue(dialog.getY() + dialog.getHeight() <= rect.getY(), + "Expected fallback above when preferred bottom side does not fit"); + dialog.dispose(); + } + + @Test + void showPopupDialogBiasTruePrefersRightInLandscapeWhenBothFit() { + implementation.setPortrait(false); + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(120, 140, 60, 40); + dialog.showPopupDialog(rect, true); + + assertTrue(dialog.getX() >= rect.getX() + rect.getWidth(), + "Expected popup on right side when prioritizeTopOrRightPosition=true in landscape"); + dialog.dispose(); + } + + @Test + void showPopupDialogBiasFalsePrefersLeftInLandscapeWhenBothFit() { + implementation.setPortrait(false); + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(120, 140, 60, 40); + dialog.showPopupDialog(rect, false); + + assertTrue(dialog.getX() + dialog.getWidth() <= rect.getX(), + "Expected popup on left side when prioritizeTopOrRightPosition=false in landscape"); + dialog.dispose(); + } + + @Test + void showPopupDialogFallsBackWhenPreferredRightDoesNotFit() { + implementation.setPortrait(false); + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + int displayWidth = implementation.getDisplayWidth(); + Rectangle rect = new Rectangle(Math.max(0, displayWidth - 8), 140, 6, 40); + dialog.showPopupDialog(rect, true); + + assertTrue(dialog.getX() + dialog.getWidth() <= rect.getX(), + "Expected fallback to left when preferred right side does not fit"); + dialog.dispose(); + } + + @Test + void showPopupDialogFallsBackWhenPreferredLeftDoesNotFit() { + implementation.setPortrait(false); + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(new FlowLayout()); + dialog.add(new Label("Popup")); + dialog.setAnimateShow(false); + + Rectangle rect = new Rectangle(2, 140, 6, 40); + dialog.showPopupDialog(rect, false); + + assertTrue(dialog.getX() >= rect.getX() + rect.getWidth(), + "Expected fallback to right when preferred left side does not fit"); + dialog.dispose(); + } + + @Test + void showPopupDialogSetsArrowTrackComponentForBothBiasModes() throws Exception { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + InteractionDialog topDialog = new InteractionDialog(); + topDialog.setUIID("PopupDialog"); + topDialog.setLayout(new FlowLayout()); + topDialog.add(new Label("Popup")); + topDialog.setAnimateShow(false); + TrackingBorder topBorder = new TrackingBorder(); + topDialog.getAllStyles().setBorder(topBorder); + Rectangle rect = new Rectangle(120, 220, 60, 40); + topDialog.showPopupDialog(rect, true); + assertNotNull(topBorder.lastTrackRect, "Expected border tracking rectangle to be set"); + assertEquals(rect.getX(), topBorder.lastTrackRect.getX()); + assertEquals(rect.getY(), topBorder.lastTrackRect.getY()); + assertTrue(topDialog.getY() + topDialog.getHeight() <= rect.getY(), + "Bias=true should place popup above in this setup"); + topDialog.dispose(); + + InteractionDialog bottomDialog = new InteractionDialog(); + bottomDialog.setUIID("PopupDialog"); + bottomDialog.setLayout(new FlowLayout()); + bottomDialog.add(new Label("Popup")); + bottomDialog.setAnimateShow(false); + TrackingBorder bottomBorder = new TrackingBorder(); + bottomDialog.getAllStyles().setBorder(bottomBorder); + bottomDialog.showPopupDialog(rect, false); + assertNotNull(bottomBorder.lastTrackRect, "Expected border tracking rectangle to be set"); + assertEquals(rect.getX(), bottomBorder.lastTrackRect.getX()); + assertEquals(rect.getY(), bottomBorder.lastTrackRect.getY()); + assertTrue(bottomDialog.getY() >= rect.getY() + rect.getHeight(), + "Bias=false should place popup below in this setup"); + bottomDialog.dispose(); + } + + private static class TrackingBorder extends Border { + private Rectangle lastTrackRect; + private boolean imageTileCalled; + + @Override + public void setTrackComponent(Rectangle trackComponent) { + lastTrackRect = trackComponent; + super.setTrackComponent(trackComponent); + } + + @Override + public void setImageBorderSpecialTile(Image tileTop, Image tileBottom, Image tileLeft, Image tileRight, Rectangle trackComponent) { + imageTileCalled = true; + super.setImageBorderSpecialTile(tileTop, tileBottom, tileLeft, tileRight, trackComponent); + } + } + + @Test + void showPopupDialogUsesTrackFallbackWhenArrowImagesMissing() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + String uiid = "UnitTestPopupNoImages"; + Hashtable themeProps = new Hashtable<>(); + themeProps.put(uiid + "ArrowBool", Boolean.TRUE); + UIManager.getInstance().addThemeProps(themeProps); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setUIID(uiid); + dialog.setAnimateShow(false); + TrackingBorder border = new TrackingBorder(); + dialog.getAllStyles().setBorder(border); + dialog.getDialogStyle().setBorder(border); + dialog.add(new Label("Popup")); + dialog.showPopupDialog(new Rectangle(120, 220, 60, 40), true); + + assertNotNull(border.lastTrackRect, "Expected tracked rectangle fallback when arrow images are missing"); + assertFalse(border.imageTileCalled, "Should not use image-border tiles when no arrow images are provided"); + dialog.dispose(); + } + + @Test + void showPopupDialogUsesImageTilesWhenArrowImagesProvided() { + Form form = new Form(new BorderLayout()); + implementation.setCurrentForm(form); + + String uiid = "UnitTestPopupWithImages"; + Hashtable themeProps = new Hashtable<>(); + themeProps.put(uiid + "ArrowBool", Boolean.TRUE); + Image img = Image.createImage(4, 4); + themeProps.put(uiid + "ArrowTopImage", img); + themeProps.put(uiid + "ArrowBottomImage", img); + themeProps.put(uiid + "ArrowLeftImage", img); + themeProps.put(uiid + "ArrowRightImage", img); + UIManager.getInstance().addThemeProps(themeProps); + + InteractionDialog dialog = new InteractionDialog(); + dialog.setUIID(uiid); + dialog.setAnimateShow(false); + TrackingBorder border = new TrackingBorder(); + dialog.getAllStyles().setBorder(border); + dialog.getDialogStyle().setBorder(border); + dialog.add(new Label("Popup")); + dialog.showPopupDialog(new Rectangle(120, 220, 60, 40), true); + + assertTrue(border.imageTileCalled, "Expected image-border tile path when arrow images are provided"); + dialog.dispose(); + } + private T getPrivateField(Object target, String name, Class type) throws Exception { Field field = target.getClass().getDeclaredField(name); field.setAccessible(true); diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java index 29d31a3a1f..6ad6468272 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java @@ -69,6 +69,7 @@ public final class Cn1ssDeviceRunner extends DeviceRunner { new BrowserComponentScreenshotTest(), new MediaPlaybackScreenshotTest(), new SheetScreenshotTest(), + new InteractionDialogPopupBiasScreenshotTest(), new ImageViewerNavigationScreenshotTest(), new TextAreaAlignmentScreenshotTest(), // Keep this as the last screenshot test; orientation changes can leak into subsequent screenshots. diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/InteractionDialogPopupBiasScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/InteractionDialogPopupBiasScreenshotTest.java new file mode 100644 index 0000000000..1fa1df25a3 --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/InteractionDialogPopupBiasScreenshotTest.java @@ -0,0 +1,89 @@ +package com.codenameone.examples.hellocodenameone.tests; + +import com.codename1.components.InteractionDialog; +import com.codename1.ui.Component; +import com.codename1.ui.Container; +import com.codename1.ui.Form; +import com.codename1.ui.Label; +import com.codename1.ui.layouts.BorderLayout; +import com.codename1.ui.layouts.BoxLayout; +import com.codename1.ui.layouts.FlowLayout; +import com.codename1.ui.util.UITimer; + +public class InteractionDialogPopupBiasScreenshotTest extends BaseTest { + private InteractionDialog northFallbackDialog; + private InteractionDialog centerTopDialog; + private InteractionDialog centerBottomDialog; + private InteractionDialog southFallbackDialog; + private Label northTarget; + private Label centerTarget; + private Label southTarget; + + @Override + public boolean runTest() { + Form form = createForm("InteractionDialog Popup", new BorderLayout(), "InteractionDialogPopupBias"); + northTarget = createTarget("NORTH TARGET"); + centerTarget = createTarget("CENTER TARGET"); + southTarget = createTarget("SOUTH TARGET"); + Container center = new Container(new FlowLayout(Component.CENTER, Component.CENTER)); + center.add(centerTarget); + form.add(BorderLayout.NORTH, wrapTarget(northTarget)); + form.add(BorderLayout.CENTER, center); + form.add(BorderLayout.SOUTH, wrapTarget(southTarget)); + form.show(); + return true; + } + + @Override + protected void registerReadyCallback(Form parent, Runnable run) { + northFallbackDialog = createDialog("1) N/T -> down"); + centerTopDialog = createDialog("2) C/T -> up"); + centerBottomDialog = createDialog("3) C/B -> down"); + southFallbackDialog = createDialog("4) S/B -> up"); + parent.revalidate(); + northFallbackDialog.showPopupDialog(northTarget, true); + centerTopDialog.showPopupDialog(centerTarget, true); + centerBottomDialog.showPopupDialog(centerTarget, false); + southFallbackDialog.showPopupDialog(southTarget, false); + UITimer.timer(600, false, parent, run); + } + + @Override + public void cleanup() { + if (northFallbackDialog != null && northFallbackDialog.isShowing()) { + northFallbackDialog.dispose(); + } + if (centerTopDialog != null && centerTopDialog.isShowing()) { + centerTopDialog.dispose(); + } + if (centerBottomDialog != null && centerBottomDialog.isShowing()) { + centerBottomDialog.dispose(); + } + if (southFallbackDialog != null && southFallbackDialog.isShowing()) { + southFallbackDialog.dispose(); + } + } + + private InteractionDialog createDialog(String text) { + InteractionDialog dialog = new InteractionDialog(); + dialog.setLayout(BoxLayout.y()); + dialog.setAnimateShow(false); + Label label = new Label(text); + label.getAllStyles().setPadding(1, 1, 1, 1); + dialog.add(label); + dialog.setDisposeWhenPointerOutOfBounds(false); + return dialog; + } + + private Label createTarget(String text) { + Label target = new Label(text); + target.getAllStyles().setPadding(2, 2, 2, 2); + return target; + } + + private Container wrapTarget(Component target) { + Container wrapper = new Container(new FlowLayout(Component.CENTER, Component.CENTER)); + wrapper.add(target); + return wrapper; + } +}