From df213ffa70760e8076e94a8aa7568ddefbbad089 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 23 May 2026 20:54:15 +0300 Subject: [PATCH] Fix #5019: revalidate lightweight Picker after shortcut button action On Android the spinner wheels could stay stale after a custom addLightweightPopupButton callback called setDate/setTime/etc. while the popup was on screen. Spinner3D.setModel only flags the scroller as needing a new preferred size; the implicit getScene().repaint() in SpinnerNode.setScrollY wasn't enough to drive a relayout, so the wheels showed an empty/stale value until the user touched them. PopupButtonActionListener now revalidates+repaints the spinner container after running the user's Runnable, which fulfils the documented contract on setDate that "if the lightweight popup is currently on screen the visible scroll wheels are also moved to the new value." The simulator was unaffected because its paint pipeline already picked up the change. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/com/codename1/ui/spinner/Picker.java | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/spinner/Picker.java b/CodenameOne/src/com/codename1/ui/spinner/Picker.java index 2150cabb8e..a316ae87f6 100644 --- a/CodenameOne/src/com/codename1/ui/spinner/Picker.java +++ b/CodenameOne/src/com/codename1/ui/spinner/Picker.java @@ -192,18 +192,33 @@ private LightweightPopupButton(String text, Runnable action, int placement, int /// Listener fired when a custom popup button is pressed. Static (rather than an anonymous /// inner class) so it does not retain a reference to the enclosing `Picker`; the only - /// state it needs is the matching `LightweightPopupButton` whose `action` it invokes. + /// state it needs is the matching `LightweightPopupButton` whose `action` it invokes + /// and the spinner `Component` to refresh after the action runs. The spinner reference + /// is dropped along with the popup dialog so it does not outlive the editing session. private static final class PopupButtonActionListener implements ActionListener { private final LightweightPopupButton popupButton; + private final Container spinnerContainer; - private PopupButtonActionListener(LightweightPopupButton popupButton) { + private PopupButtonActionListener(LightweightPopupButton popupButton, Container spinnerContainer) { this.popupButton = popupButton; + this.spinnerContainer = spinnerContainer; } @Override public void actionPerformed(ActionEvent evt) { - if (popupButton.action != null) { - popupButton.action.run(); + if (popupButton.action == null) { + return; + } + popupButton.action.run(); + // Force a layout + repaint of the spinner so a setDate / setTime / + // setSelectedString / setDuration call inside the action propagates + // to the visible wheels. Spinner3D.setModel only flags the scroller + // as needing a new preferred size; on Android nothing else triggers + // the relayout, so the wheels stay stale until the user touches + // them. Issue #5019. + if (spinnerContainer != null) { + spinnerContainer.revalidate(); + spinnerContainer.repaint(); } } } @@ -727,8 +742,9 @@ protected void deinitialize() { .setBgTransparency(0) .setMargin(0) .setPaddingMillimeters(3f, 0); - Container topCustomButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.ABOVE_SPINNER, isTablet); - Container bottomCustomButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.BELOW_SPINNER, isTablet); + final Container spinnerContainer = spinnerC instanceof Container ? (Container) spinnerC : null; + Container topCustomButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.ABOVE_SPINNER, isTablet, spinnerContainer); + Container bottomCustomButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.BELOW_SPINNER, isTablet, spinnerContainer); if (topCustomButtons != null || bottomCustomButtons != null) { Container spinnerSection = new Container(new BorderLayout()); spinnerSection.add(BorderLayout.CENTER, wrapper); @@ -824,7 +840,7 @@ public void actionPerformed(ActionEvent evt) { west.add(nextButton); } - Container centerButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.BETWEEN_CANCEL_AND_DONE, isTablet); + Container centerButtons = createLightweightPopupButtonRow(LightweightPopupButtonPlacement.BETWEEN_CANCEL_AND_DONE, isTablet, spinnerContainer); Container buttonBar = BorderLayout.centerEastWest(centerButtons, doneButton, west); buttonBar.setUIID(isTablet ? "PickerButtonBarTablet" : "PickerButtonBar"); dlg.getContentPane().add(BorderLayout.NORTH, buttonBar); @@ -898,7 +914,7 @@ public void actionPerformed(ActionEvent evt) { updateValue(); } - private Container createLightweightPopupButtonRow(int placement, boolean isTablet) { + private Container createLightweightPopupButtonRow(int placement, boolean isTablet, Container spinnerContainer) { Container left = null; Container center = null; Container right = null; @@ -907,7 +923,7 @@ private Container createLightweightPopupButtonRow(int placement, boolean isTable continue; } Button button = new Button(entry.text, isTablet ? "PickerButtonTablet" : "PickerButton"); - button.addActionListener(new PopupButtonActionListener(entry)); + button.addActionListener(new PopupButtonActionListener(entry, spinnerContainer)); switch (entry.alignment) { case Component.CENTER: if (center == null) {