ProgressView spinner theming

- New SpinnerSettings which integrates with ThemeSettings
- Support infra around spinner theming
- Relates #995
This commit is contained in:
Janne Valkealahti
2024-02-04 14:56:55 +00:00
parent 95aaa865c5
commit abc4ffaaa3
7 changed files with 181 additions and 12 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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<Context> 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);
}
/**

View File

@@ -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);
}
}
}

View File

@@ -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}.
*

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}