diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java index a14d88fa..8ff0f344 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.shell.style.FigureSettings; +import org.springframework.shell.style.SpinnerSettings; import org.springframework.shell.style.StyleSettings; import org.springframework.shell.style.TemplateExecutor; import org.springframework.shell.style.Theme; @@ -90,7 +91,7 @@ public class ThemingAutoConfigurationTests { static class MyThemeSettings extends ThemeSettings { MyThemeSettings() { - super(StyleSettings.defaults(), FigureSettings.defaults()); + super(StyleSettings.defaults(), FigureSettings.defaults(), SpinnerSettings.defaults()); } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractControl.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractControl.java index 6e5fb011..3f36a7cf 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractControl.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractControl.java @@ -145,4 +145,29 @@ public abstract class AbstractControl implements Control { return getThemeResolvedValues(tag).map(ResolvedValues::background).orElse(fallbackColor); } + /** + * Resolve {@link Spinner} using existing {@link ThemeResolver} and {@code theme name}. + * {@code defaultSpinner} is used if it's not {@code null}. {@code fallbackSpinner} is + * used if theme resolver cannot be used. + * + * @param tag the spinner tag to use + * @param defaultSpinner the default spinner to use + * @param fallbackSpinner the fallback spinner to use + * @return resolved spinner + */ + protected Spinner resolveThemeSpinner(String tag, Spinner defaultSpinner, Spinner fallbackSpinner) { + if (defaultSpinner != null) { + return defaultSpinner; + } + Spinner spinner = null; + ThemeResolver themeResolver = getThemeResolver(); + if (themeResolver != null) { + spinner = getThemeResolver().resolveSpinnerTag(tag); + } + if (spinner == null) { + spinner = fallbackSpinner; + } + return spinner; + } + } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ProgressView.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ProgressView.java index bc7ed6ac..dfbc65fd 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ProgressView.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ProgressView.java @@ -28,6 +28,7 @@ import org.springframework.shell.component.view.control.cell.TextCell; import org.springframework.shell.component.view.screen.Screen; import org.springframework.shell.geom.HorizontalAlign; import org.springframework.shell.geom.Rectangle; +import org.springframework.shell.style.SpinnerSettings; import org.springframework.shell.style.ThemeResolver; import org.springframework.util.Assert; @@ -75,10 +76,10 @@ public class ProgressView extends BoxView { TextCell cell = TextCell.of(item, ctx -> { int frame = 0; - Spinner spin = ctx.getSpinner(); - if (spin == null) { - spin = Spinner.of(Spinner.LINE1, 130); - } + // via view setting, then via theming, then fallback default + Spinner spin = ctx.resolveThemeSpinner(SpinnerSettings.TAG_DOT, ctx.getSpinner(), + Spinner.of(Spinner.LINE1, 130)); + if (ctx.getState().running()) { // we know start time and current update time, // calculate elapsed time "frame" to pick rolling @@ -343,6 +344,11 @@ public class ProgressView extends BoxView { return ProgressView.this.resolveThemeStyle(tag, defaultStyle); } + @Override + public Spinner resolveThemeSpinner(String tag, Spinner defaultSpinner, Spinner fallbackSpinner) { + return ProgressView.this.resolveThemeSpinner(tag, defaultSpinner, fallbackSpinner); + } + @Override public Spinner getSpinner() { return ProgressView.this.spinner; @@ -393,6 +399,8 @@ public class ProgressView extends BoxView { */ int resolveThemeStyle(String tag, int defaultStyle); + Spinner resolveThemeSpinner(String tag, Spinner defaultSpinner, Spinner fallbackSpinner); + } /** diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/SpinnerSettings.java b/spring-shell-core/src/main/java/org/springframework/shell/style/SpinnerSettings.java new file mode 100644 index 00000000..ba6475b7 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/style/SpinnerSettings.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.style; + +import org.springframework.shell.component.view.control.Spinner; + +/** + * Base class defining a settings for spinners. + * + * @author Janne Valkealahti + */ +public abstract class SpinnerSettings { + + /** + * Spinner with line characters. + */ + public final static String TAG_LINE = "line"; + + /** + * Spinner with dot characters. + */ + public final static String TAG_DOT = "dot"; + + public Spinner line() { + return Spinner.of(Spinner.LINE1, 130); + } + + public Spinner dot() { + return Spinner.of(Spinner.DOTS1, 80); + } + + public Spinner resolveTag(String tag) { + switch (tag) { + case TAG_LINE: + return line(); + case TAG_DOT: + return dot(); + } + throw new IllegalArgumentException(String.format("Unknown tag '%s'", tag)); + } + + public static String[] tags() { + return new String[] { + TAG_LINE, + TAG_DOT, + }; + } + + public static SpinnerSettings defaults() { + return new DefaultSpinnerSettings(); + } + + public static SpinnerSettings dump() { + return new DumpSpinnerSettings(); + } + + private static class DefaultSpinnerSettings extends SpinnerSettings { + } + + private static class DumpSpinnerSettings extends SpinnerSettings { + + @Override + public Spinner dot() { + return Spinner.of(Spinner.DOTS14, 200); + } + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java index 2ad9ff14..76c1c1f9 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.jline.utils.AttributedString; import org.jline.utils.AttributedStyle; import org.jline.utils.Colors; +import org.springframework.shell.component.view.control.Spinner; import org.springframework.util.StringUtils; /** @@ -141,6 +142,16 @@ public class ThemeResolver { return theme.getSettings().figures().resolveTag(tag); } + /** + * Resolve spinner from a tag with activated theme. + * + * @param tag the tag + * @return a spinner + */ + public Spinner resolveSpinnerTag(String tag) { + return theme.getSettings().spinners().resolveTag(tag); + } + /** * Resolve {@link AttributedStyle} from a {@code spec}. * diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java index 21d4189d..40ea94ad 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ public abstract class ThemeSettings { private StyleSettings styleSettings; private FigureSettings figureSettings; + private SpinnerSettings spinnerSettings; /** * Creates theme settings with dump styles and figures. @@ -32,6 +33,15 @@ public abstract class ThemeSettings { this(StyleSettings.dump(), FigureSettings.dump()); } + /** + * Creates theme settings with styles. + * + * @param styleSettings style settings + */ + public ThemeSettings(StyleSettings styleSettings) { + this(styleSettings, FigureSettings.dump(), SpinnerSettings.dump()); + } + /** * Creates theme settings with styles and figures. * @@ -39,8 +49,20 @@ public abstract class ThemeSettings { * @param figureSettings figure settings */ public ThemeSettings(StyleSettings styleSettings, FigureSettings figureSettings) { + this(styleSettings, figureSettings, SpinnerSettings.dump()); + } + + /** + * Creates theme settings with styles, figures and spinners. + * + * @param styleSettings style settings + * @param figureSettings figure settings + * @param spinnerSettings spinner settings + */ + public ThemeSettings(StyleSettings styleSettings, FigureSettings figureSettings, SpinnerSettings spinnerSettings) { this.styleSettings = styleSettings; this.figureSettings = figureSettings; + this.spinnerSettings = spinnerSettings; } /** @@ -61,13 +83,22 @@ public abstract class ThemeSettings { return this.figureSettings; } + /** + * Gets a {@link SpinnerSettings}. + * + * @return spinner settings + */ + public SpinnerSettings spinners() { + return this.spinnerSettings; + } + /** * Gets a default theme settings. * * @return default theme settings */ public static ThemeSettings defaults() { - return new DefaultThemeSettings(StyleSettings.defaults(), FigureSettings.defaults()); + return new DefaultThemeSettings(StyleSettings.defaults(), FigureSettings.defaults(), SpinnerSettings.defaults()); } /** @@ -85,8 +116,8 @@ public abstract class ThemeSettings { super(); } - DefaultThemeSettings(StyleSettings styleSettings, FigureSettings figureSettings) { - super(styleSettings, figureSettings); + DefaultThemeSettings(StyleSettings styleSettings, FigureSettings figureSettings, SpinnerSettings spinnerSettings) { + super(styleSettings, figureSettings, spinnerSettings); } } } diff --git a/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/ComponentUiCommands.java b/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/ComponentUiCommands.java index 457b6095..0f12ae4a 100644 --- a/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/ComponentUiCommands.java +++ b/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/ComponentUiCommands.java @@ -133,7 +133,8 @@ public class ComponentUiCommands extends AbstractShellComponent { @Command(command = "componentui progress2") public void progress2() { - ProgressView view = new ProgressView(0, 100, ProgressViewItem.ofText(10, HorizontalAlign.LEFT), + ProgressView view = new ProgressView(0, 100, + ProgressViewItem.ofText(10, HorizontalAlign.LEFT), ProgressViewItem.ofSpinner(3, HorizontalAlign.LEFT), ProgressViewItem.ofPercent(0, HorizontalAlign.RIGHT)); view.setDescription("name"); @@ -154,4 +155,15 @@ public class ComponentUiCommands extends AbstractShellComponent { runProgress(view); } + @Command(command = "componentui progress4") + public void progress4() { + ProgressView view = new ProgressView(); + view.setDescription("name"); + view.setRect(0, 0, 20, 1); + view.setThemeResolver(getThemeResolver()); + view.start(); + + runProgress(view); + } + }