Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 33 additions & 2 deletions CodenameOne/src/com/codename1/ui/plaf/UIManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ public class UIManager {
private Style defaultStyle = new Style();
private Style defaultSelectedStyle = new Style();
private boolean useLargerTextScale;
/// Tracks the original (unscaled) Font we replaced in themeProps when
/// [#applyLargerTextScaleToThemeFonts] last ran. Without this, each scale
/// change derives from the previously-scaled font and compounds, so going
/// XL -> XXL over-scales and going XXL -> Large never shrinks back. The
/// parallel scaledFontDerived map records the Font we wrote, so we only
/// restore entries that the theme has not overwritten in the meantime.
private final Map<String, Font> scaledFontOriginals = new HashMap<String, Font>();
private final Map<String, Font> scaledFontDerived = new HashMap<String, Font>();
/// The resource bundle allows us to implicitly localize the UI on the fly, once its
/// installed all internal application strings query the resource bundle and extract
/// their values from this table if applicable.
Expand Down Expand Up @@ -2042,6 +2050,26 @@ private Font scaleFontForLargerText(Font font, float scale) {
}

private void applyLargerTextScaleToThemeFonts() {
// Roll back any prior scaling we applied so this pass always derives
// from the original installed font. Without the rollback, repeated
// refreshes compound (each scale multiplies the previously-derived
// pixel size) and a return to scale 1.0 never actually shrinks fonts.
if (!scaledFontOriginals.isEmpty()) {
for (Map.Entry<String, Font> entry : scaledFontOriginals.entrySet()) {
String key = entry.getKey();
Object current = themeProps.get(key);
Font derived = scaledFontDerived.get(key);
// Only restore when the theme still holds the Font we wrote;
// an intervening setThemeProps/addThemeProps may have replaced
// it with a new original we must keep.
if (current == derived) { //NOPMD CompareObjectsWithEquals
themeProps.put(key, entry.getValue());
}
}
scaledFontOriginals.clear();
scaledFontDerived.clear();
}

float scale = getEffectiveLargerTextScale();
if (scale <= 1f) {
return;
Expand All @@ -2052,8 +2080,11 @@ private void applyLargerTextScaleToThemeFonts() {
}
Object value = entry.getValue();
if (value instanceof Font) {
Font scaled = scaleFontForLargerText((Font) value, scale);
if (scaled != value) { //NOPMD CompareObjectsWithEquals
Font original = (Font) value;
Font scaled = scaleFontForLargerText(original, scale);
if (scaled != original) { //NOPMD CompareObjectsWithEquals
scaledFontOriginals.put(entry.getKey(), original);
scaledFontDerived.put(entry.getKey(), scaled);
entry.setValue(scaled);
}
}
Expand Down
35 changes: 27 additions & 8 deletions Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -5726,7 +5726,7 @@ private void addSkinName(String f) {
}
}

private void deepRevaliate(com.codename1.ui.Container c) {
private static void deepRevaliate(com.codename1.ui.Container c) {
c.setShouldCalcPreferredSize(true);
for (int iter = 0; iter < c.getComponentCount(); iter++) {
com.codename1.ui.Component cmp = c.getComponentAt(iter);
Expand Down Expand Up @@ -5933,17 +5933,36 @@ static Rectangle parsePersistedBounds(String s) {
private void refreshThemeOnly() {
Display.getInstance().callSerially(new Runnable() {
public void run() {
UIManager.getInstance().refreshTheme();
Form curr = Display.getInstance().getCurrent();
if (curr != null) {
deepRevaliate(curr);
curr.revalidate();
curr.repaint();
}
applyThemeOnlyRefresh(Display.getInstance().getCurrent());
}
});
}

/// Refreshes the active theme on the given form in place. Extracted so
/// the simulator's Dark/Light + Larger Text menu actions and the unit
/// test that guards them share the exact same sequence:
///
/// 1. `UIManager.refreshTheme()` rebuilds themeProps and clears the
/// style cache.
/// 2. `Form.refreshTheme(true)` walks the live component tree and
/// re-resolves each Style, so the displayed components actually
/// pick up the new fonts / colors. Without this step the user sees
/// no change until they navigate away and back or restart the
/// simulator -- the bug reported in issue #4963.
/// 3. `deepRevaliate` + `revalidate` + `repaint` redo layout against
/// the new font metrics so a taller font actually claims more
/// pixels on screen.
static void applyThemeOnlyRefresh(Form curr) {
UIManager.getInstance().refreshTheme();
if (curr == null) {
return;
}
curr.refreshTheme(true);
deepRevaliate(curr);
curr.revalidate();
curr.repaint();
}


private ArrayList<Runnable> deinitializeHooks = new ArrayList<>();
public void addDeinitializeHook(Runnable r) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.codename1.junit.UITestBase;
import com.codename1.testing.TestCodenameOneImplementation;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import java.util.Hashtable;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -69,4 +71,99 @@ public void testLargeTextScaleDisabledByDefault() {
Font scaledFont = manager.getComponentStyle("Title").getFont();
assertEquals(12f, scaledFont.getPixelSize(), 0.01f);
}

/// Walking the scale up and back down via repeated [UIManager#refreshTheme]
/// calls (the path the simulator's Larger Text menu takes) must always
/// derive sizes from the original installed font, not from the
/// previously-scaled font. Without the rollback in
/// [UIManager#applyLargerTextScaleToThemeFonts] each step compounded the
/// previous one: XL -> XXL over-scaled, and a return to scale 1.0 never
/// shrank the fonts back. Regression cover for issue #4963.
@Test
public void testRepeatedScaleChangesDoNotCompound() {
TestCodenameOneImplementation impl = implementation;
UIManager manager = UIManager.getInstance();
manager.setUseLargerTextScale(true);

Font baseFont = Font.createTrueTypeFont(Font.NATIVE_MAIN_REGULAR, Font.NATIVE_MAIN_REGULAR)
.derive(20f, Font.STYLE_PLAIN);
Hashtable theme = new Hashtable();
theme.put("Button.font", baseFont);

impl.setLargerTextEnabled(false);
impl.setLargerTextScale(1.0f);
manager.setThemeProps(theme);
assertEquals(20f, manager.getComponentStyle("Button").getFont().getPixelSize(), 0.01f);

// First bump: 1.0 -> 1.5 should yield 30.
impl.setLargerTextEnabled(true);
impl.setLargerTextScale(1.5f);
manager.refreshTheme();
assertEquals(30f, manager.getComponentStyle("Button").getFont().getPixelSize(), 0.01f);

// Second bump: 1.5 -> 2.0 must yield 40 (20 * 2.0), not 60 (30 * 2.0).
impl.setLargerTextScale(2.0f);
manager.refreshTheme();
assertEquals(40f, manager.getComponentStyle("Button").getFont().getPixelSize(), 0.01f);

// Step back down: 2.0 -> 1.25 must yield 25, not some compounded value.
impl.setLargerTextScale(1.25f);
manager.refreshTheme();
assertEquals(25f, manager.getComponentStyle("Button").getFont().getPixelSize(), 0.01f);

// Return to default scale: fonts must shrink all the way back to 20.
impl.setLargerTextEnabled(false);
impl.setLargerTextScale(1.0f);
manager.refreshTheme();
assertEquals(20f, manager.getComponentStyle("Button").getFont().getPixelSize(), 0.01f);
}

/// Calling [UIManager#refreshTheme] alone rebuilds the theme cache but
/// does not push the rebuilt styles down to components that already
/// resolved their styles. The simulator's Larger Text menu therefore has
/// to follow `UIManager.refreshTheme()` with `Form.refreshTheme(true)`
/// (see `JavaSEPort.refreshThemeOnly`) so the components on screen
/// actually pick up the new fonts. This test pins that contract by
/// verifying an existing Label's resolved font tracks the scale through
/// a full up-and-back-down cycle. Issue #4963.
@Test
public void testFormRefreshThemePropagatesScaleChange() {
TestCodenameOneImplementation impl = implementation;
UIManager manager = UIManager.getInstance();
manager.setUseLargerTextScale(true);

Font baseFont = Font.createTrueTypeFont(Font.NATIVE_MAIN_REGULAR, Font.NATIVE_MAIN_REGULAR)
.derive(18f, Font.STYLE_PLAIN);
Hashtable theme = new Hashtable();
theme.put("Label.font", baseFont);

impl.setLargerTextEnabled(false);
impl.setLargerTextScale(1.0f);
manager.setThemeProps(theme);

Form form = new Form();
Label label = new Label("Hello");
form.addComponent(label);
// Force the style chain to be resolved so the label is holding a
// Style instance from the pre-refresh theme.
assertEquals(18f, label.getStyle().getFont().getPixelSize(), 0.01f);

impl.setLargerTextEnabled(true);
impl.setLargerTextScale(1.5f);
manager.refreshTheme();
form.refreshTheme(true);
assertEquals(27f, label.getStyle().getFont().getPixelSize(), 0.01f);

impl.setLargerTextScale(2.0f);
manager.refreshTheme();
form.refreshTheme(true);
assertEquals(36f, label.getStyle().getFont().getPixelSize(), 0.01f);

impl.setLargerTextEnabled(false);
impl.setLargerTextScale(1.0f);
manager.refreshTheme();
form.refreshTheme(true);
assertEquals(18f, label.getStyle().getFont().getPixelSize(), 0.01f);
}

}
Loading