diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/TerminalUIAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/TerminalUIAutoConfiguration.java new file mode 100644 index 00000000..e0b48070 --- /dev/null +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/TerminalUIAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 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.boot; + +import org.jline.terminal.Terminal; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.shell.component.view.TerminalUI; +import org.springframework.shell.component.view.TerminalUIBuilder; +import org.springframework.shell.component.view.TerminalUICustomizer; +import org.springframework.shell.style.ThemeActive; +import org.springframework.shell.style.ThemeResolver; + +@AutoConfiguration +@ConditionalOnClass(TerminalUI.class) +public class TerminalUIAutoConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + public TerminalUIBuilder terminalUIBuilder(Terminal terminal, ThemeResolver themeResolver, ThemeActive themeActive, + ObjectProvider customizerProvider) { + TerminalUIBuilder builder = new TerminalUIBuilder(terminal); + builder = builder.themeName(themeActive.get()); + builder = builder.themeResolver(themeResolver); + builder = builder.customizers(customizerProvider.orderedStream().toList()); + return builder; + } + +} diff --git a/spring-shell-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-shell-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c095b461..83b74c66 100644 --- a/spring-shell-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-shell-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -14,3 +14,4 @@ org.springframework.shell.boot.StandardAPIAutoConfiguration org.springframework.shell.boot.ThemingAutoConfiguration org.springframework.shell.boot.StandardCommandsAutoConfiguration org.springframework.shell.boot.ComponentFlowAutoConfiguration +org.springframework.shell.boot.TerminalUIAutoConfiguration diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/TerminalUIAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/TerminalUIAutoConfigurationTests.java new file mode 100644 index 00000000..c8ccb03d --- /dev/null +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/TerminalUIAutoConfigurationTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 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.boot; + +import java.util.Set; + +import org.jline.terminal.Size; +import org.jline.terminal.Terminal; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.shell.component.view.TerminalUI; +import org.springframework.shell.component.view.TerminalUIBuilder; +import org.springframework.shell.component.view.TerminalUICustomizer; +import org.springframework.shell.style.ThemeActive; +import org.springframework.shell.style.ThemeRegistry; +import org.springframework.shell.style.ThemeResolver; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TerminalUIAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TerminalUIAutoConfiguration.class)); + + @Test + public void terminalUICreated() { + this.contextRunner + .withUserConfiguration(MockConfiguration.class) + .run(context -> { + assertThat(context).hasSingleBean(TerminalUIBuilder.class); + }); + } + + @Test + @SuppressWarnings("unchecked") + public void canCustomize() { + this.contextRunner + .withUserConfiguration(TestConfiguration.class, MockConfiguration.class) + .run(context -> { + TerminalUIBuilder builder = context.getBean(TerminalUIBuilder.class); + Set customizers = (Set) ReflectionTestUtils + .getField(builder, "customizers"); + assertThat(customizers).hasSize(1); + }); + } + + @Configuration(proxyBeanMethods = false) + static class MockConfiguration { + + @Bean + Terminal mockTerminal() { + Terminal terminal = mock(Terminal.class); + when(terminal.getBufferSize()).thenReturn(new Size()); + return terminal; + } + + @Bean + ThemeResolver mockThemeResolver() { + return new ThemeResolver(new ThemeRegistry(), "default"); + } + + @Bean + ThemeActive themeActive() { + return () -> { + return "default"; + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + + @Bean + TerminalUICustomizer terminalUICustomizer() { + return new TestTerminalUICustomizer(); + } + } + + static class TestTerminalUICustomizer implements TerminalUICustomizer { + + @Override + public void customize(TerminalUI terminalUI) { + terminalUI.setThemeName("test"); + } + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUI.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUI.java index 18c5c6cb..9508e057 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUI.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUI.java @@ -47,6 +47,7 @@ import org.springframework.shell.component.view.event.MouseHandler; import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerResult; import org.springframework.shell.component.view.screen.DefaultScreen; import org.springframework.shell.geom.Rectangle; +import org.springframework.shell.style.ThemeResolver; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -73,6 +74,8 @@ public class TerminalUI implements ViewService { private final KeyBinder keyBinder; private DefaultEventLoop eventLoop = new DefaultEventLoop(); private View focus = null; + private ThemeResolver themeResolver; + private String themeName = "default"; /** * Constructs a handler with a given terminal. @@ -135,6 +138,64 @@ public class TerminalUI implements ViewService { getEventLoop().dispatch(ShellMessageBuilder.ofRedraw()); } + /** + * Sets a {@link ThemeResolver}. + * + * @param themeResolver the theme resolver + */ + public void setThemeResolver(ThemeResolver themeResolver) { + this.themeResolver = themeResolver; + } + + /** + * Sets a {@link ThemeResolver}. + * + * @return a theme resolver + */ + public ThemeResolver getThemeResolver() { + return themeResolver; + } + + /** + * Sets a {@code theme name}. + * + * @param themeName the theme name + */ + public void setThemeName(String themeName) { + this.themeName = themeName; + } + + /** + * Gets a {@code theme name}. + * + * @return a theme name + */ + public String getThemeName() { + return themeName; + } + + /** + * Gets a {@link ViewService}. + * + * @return a view service + */ + public ViewService getViewService() { + return this; + } + + /** + * Configure view for {@link EventLoop}, {@link ThemeResolver}, + * {@code theme name} and {@link ViewService}. + * + * @param view the view to configure + */ + public void configure(View view) { + view.setEventLoop(eventLoop); + view.setThemeResolver(themeResolver); + view.setThemeName(themeName); + view.setViewService(getViewService()); + } + public void setFocus(@Nullable View view) { if (focus != null) { focus.focus(focus, false); diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUIBuilder.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUIBuilder.java new file mode 100644 index 00000000..48360430 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUIBuilder.java @@ -0,0 +1,150 @@ +/* + * Copyright 2023 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.component.view; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.jline.terminal.Terminal; + +import org.springframework.shell.style.ThemeResolver; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Builder that can be used to configure and create a {@link TerminalUI}. + * + * @author Janne Valkealahti + */ +public class TerminalUIBuilder { + + private final Terminal terminal; + private final Set customizers; + private final ThemeResolver themeResolver; + private final String themeName; + + /** + * Create a new {@link TerminalUIBuilder} instance. + * + * @param terminal the terminal + * @param customizers any {@link TerminalUICustomizer TerminalUICustomizers} + * that should be applied when the {@link TerminalUI} is + * built + */ + public TerminalUIBuilder(Terminal terminal, TerminalUICustomizer... customizers) { + this.terminal = terminal; + this.customizers = copiedSetOf(customizers); + this.themeResolver = null; + this.themeName = null; + } + + /** + * Create a new {@link TerminalUIBuilder} instance. + * + * @param terminal the terminal + * @param customizers any {@link TerminalUICustomizer TerminalUICustomizers} + * that should be applied when the {@link TerminalUI} is + * built + * @param themeResolver the theme resolver + * @param themeName the theme name + */ + public TerminalUIBuilder(Terminal terminal, Set customizers, ThemeResolver themeResolver, + String themeName) { + this.terminal = terminal; + this.customizers = customizers; + this.themeResolver = themeResolver; + this.themeName = themeName; + } + + /** + * Sets a {@link ThemeResolver} for {@link TerminalUI} to build. + * + * @param themeResolver the theme resolver + * @return a new builder instance + */ + public TerminalUIBuilder themeResolver(ThemeResolver themeResolver) { + return new TerminalUIBuilder(terminal, customizers, themeResolver, themeName); + } + + /** + * Sets a {@code theme name} for {@link TerminalUI} to build. + * + * @param themeName the theme name + * @return a new builder instance + */ + public TerminalUIBuilder themeName(String themeName) { + return new TerminalUIBuilder(terminal, customizers, themeResolver, themeName); + } + + /** + * Set the {@link TerminalUICustomizer TerminalUICustomizer} that should be + * applied to the {@link TerminalUI}. Customizers are applied in the order that they + * were added after builder configuration has been applied. Setting this value will + * replace any previously configured customizers. + * + * @param customizers the customizers to set + * @return a new builder instance + */ + public TerminalUIBuilder customizers(Collection customizers) { + Assert.notNull(customizers, "Customizers must not be null"); + return new TerminalUIBuilder(terminal, copiedSetOf(customizers), themeResolver, themeName); + } + + /** + * Build a new {@link TerminalUI} instance and configure it using this builder. + * + * @return a configured {@link TerminalUI} instance. + */ + public TerminalUI build() { + return configure(new TerminalUI(terminal)); + } + + /** + * Configure the provided {@link TerminalUI} instance using this builder. + * + * @param the type of terminal ui + * @param terminalUI the {@link TerminalUI} to configure + * @return the terminal ui instance + */ + public T configure(T terminalUI) { + if (themeResolver != null) { + terminalUI.setThemeResolver(themeResolver); + } + if (StringUtils.hasText(themeName)) { + terminalUI.setThemeName(themeName); + } + if (!CollectionUtils.isEmpty(customizers)) { + for (TerminalUICustomizer customizer : customizers) { + customizer.customize(terminalUI); + } + } + return terminalUI; + } + + @SuppressWarnings("unchecked") + private Set copiedSetOf(T... items) { + return copiedSetOf(Arrays.asList(items)); + } + + private Set copiedSetOf(Collection collection) { + return Collections.unmodifiableSet(new LinkedHashSet<>(collection)); + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUICustomizer.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUICustomizer.java new file mode 100644 index 00000000..2ae67e3b --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/TerminalUICustomizer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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.component.view; + +/** + * Callback interface that can be used to customize a {@link TerminalUI}. + * + * @author Janne Valkealahti + * @see TerminalUIBuilder + */ +@FunctionalInterface +public interface TerminalUICustomizer { + + /** + * Callback to customize a {@link TerminalUI} instance. + * + * @param terminalUI the terminal ui to customize + */ + void customize(TerminalUI terminalUI); + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractView.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractView.java index 20ba694a..44754335 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractView.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AbstractView.java @@ -263,6 +263,7 @@ public abstract class AbstractView extends AbstractControl implements View { * * @param viewService the view service */ + @Override public void setViewService(ViewService viewService) { this.viewService = viewService; } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/View.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/View.java index aff2e8e9..11cbc1ca 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/View.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/View.java @@ -89,6 +89,13 @@ public interface View extends Control { */ void setEventLoop(@Nullable EventLoop eventLoop); + /** + * Sets a {@link ViewService}. + * + * @param viewService the view service + */ + void setViewService(ViewService viewService); + /** * Get supported commands. * diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java index 99897bc9..1c0dd026 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.jline.terminal.Terminal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +28,7 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.shell.component.message.ShellMessageBuilder; import org.springframework.shell.component.view.TerminalUI; +import org.springframework.shell.component.view.TerminalUIBuilder; import org.springframework.shell.component.view.control.AppView; import org.springframework.shell.component.view.control.AppView.AppViewEvent; import org.springframework.shell.component.view.control.BoxView; @@ -52,10 +52,10 @@ import org.springframework.shell.component.view.event.KeyEvent.Key; import org.springframework.shell.component.view.event.KeyEvent.KeyMask; import org.springframework.shell.component.view.screen.Screen; import org.springframework.shell.component.view.screen.Screen.Writer; +import org.springframework.shell.component.view.screen.ScreenItem; import org.springframework.shell.geom.HorizontalAlign; import org.springframework.shell.geom.Rectangle; import org.springframework.shell.geom.VerticalAlign; -import org.springframework.shell.component.view.screen.ScreenItem; import org.springframework.shell.samples.catalog.scenario.Scenario; import org.springframework.shell.samples.catalog.scenario.ScenarioComponent; import org.springframework.shell.style.ThemeResolver; @@ -79,7 +79,6 @@ public class Catalog { // mapping from category name to scenarios(can belong to multiple categories) private final Map> categoryMap = new TreeMap<>(); - private final Terminal terminal; private View currentScenarioView = null; private TerminalUI ui; private ListView categories; @@ -88,9 +87,10 @@ public class Catalog { private EventLoop eventLoop; private ThemeResolver themeResolver; private String activeThemeName = "default"; + private TerminalUIBuilder terminalUIBuilder; - public Catalog(Terminal terminal, List scenarios, ThemeResolver themeResolver) { - this.terminal = terminal; + public Catalog(TerminalUIBuilder terminalUIBuilder, ThemeResolver themeResolver, List scenarios) { + this.terminalUIBuilder = terminalUIBuilder; this.themeResolver = themeResolver; mapScenarios(scenarios); } @@ -122,11 +122,9 @@ public class Catalog { * Main run loop. Builds the ui and exits when user requests exit. */ public void run() { - ui = new TerminalUI(terminal); + ui = terminalUIBuilder.build(); eventLoop = ui.getEventLoop(); app = buildScenarioBrowser(eventLoop, ui); - app.setThemeResolver(themeResolver); - app.setThemeName(activeThemeName); // handle logic to switch between main scenario browser // and currently active scenario @@ -155,14 +153,12 @@ public class Catalog { private AppView buildScenarioBrowser(EventLoop eventLoop, TerminalUI component) { // category selector on left, scenario selector on right GridView grid = new GridView(); - grid.setThemeResolver(themeResolver); - grid.setThemeName(activeThemeName); - grid.setEventLoop(eventLoop); + component.configure(grid); grid.setRowSize(0); grid.setColumnSize(30, 0); - categories = buildCategorySelector(eventLoop); - scenarios = buildScenarioSelector(eventLoop); + categories = buildCategorySelector(); + scenarios = buildScenarioSelector(); grid.addItem(categories, 0, 0, 1, 1, 0, 0); grid.addItem(scenarios, 0, 1, 1, 1, 0, 0); @@ -172,16 +168,16 @@ public class Catalog { // we use main app view to represent scenario browser AppView app = new AppView(grid, menuBar, statusBar); - app.setThemeResolver(themeResolver); - app.setThemeName(activeThemeName); - app.setEventLoop(eventLoop); + component.configure(app); // handle event when scenario is chosen eventLoop.onDestroy(eventLoop.viewEvents(LISTVIEW_SCENARIO_TYPEREF, scenarios) .subscribe(event -> { - View view = event.args().item().scenario() - .configure(ui, eventLoop, themeResolver, activeThemeName) - .build(); + View view = event.args().item().scenario().configure(ui).build(); + ui.configure(view); + // View view = event.args().item().scenario() + // .configure(ui, eventLoop, themeResolver, activeThemeName) + // .build(); component.setRoot(view, true); currentScenarioView = view; })); @@ -225,11 +221,10 @@ public class Catalog { return app; } - private ListView buildCategorySelector(EventLoop eventLoop) { + private ListView buildCategorySelector() { ListView categories = new ListView<>(); - categories.setThemeResolver(themeResolver); - categories.setThemeName(activeThemeName); - categories.setEventLoop(eventLoop); + ui.configure(categories); + List items = List.copyOf(categoryMap.keySet()); categories.setItems(items); categories.setTitle("Categories"); @@ -261,11 +256,9 @@ public class Catalog { } } - private ListView buildScenarioSelector(EventLoop eventLoop) { + private ListView buildScenarioSelector() { ListView scenarios = new ListView<>(); - scenarios.setThemeResolver(themeResolver); - scenarios.setThemeName(activeThemeName); - scenarios.setEventLoop(eventLoop); + ui.configure(scenarios); scenarios.setTitle("Scenarios"); scenarios.setFocusedTitleStyle(ScreenItem.STYLE_BOLD); scenarios.setShowBorder(true); @@ -287,9 +280,7 @@ public class Catalog { private DialogView buildAboutDialog() { ButtonView button = new ButtonView("OK"); - button.setThemeResolver(themeResolver); - button.setThemeName(activeThemeName); - button.setEventLoop(eventLoop); + ui.configure(button); BoxView content = new BoxView(); content.setDrawFunction((screen, rect) -> { @@ -297,11 +288,7 @@ public class Catalog { return rect; }); DialogView dialog = new DialogView(content, button); - dialog.setThemeResolver(themeResolver); - dialog.setThemeName(activeThemeName); - dialog.setEventLoop(eventLoop); - dialog.setViewService(ui); - + ui.configure(dialog); return dialog; } @@ -329,18 +316,14 @@ public class Catalog { MenuItem.of("About", MenuItemCheckStyle.NOCHECK, aboutAction)) ); - menuBar.setThemeResolver(themeResolver); - menuBar.setThemeName(activeThemeName); - menuBar.setEventLoop(eventLoop); + ui.configure(menuBar); return menuBar; } private StatusBarView buildStatusBar(EventLoop eventLoop) { Runnable quitAction = () -> requestQuit(); StatusBarView statusBar = new StatusBarView(); - statusBar.setThemeResolver(themeResolver); - statusBar.setThemeName(activeThemeName); - statusBar.setEventLoop(eventLoop); + ui.configure(statusBar); StatusItem item1 = new StatusBarView.StatusItem("CTRL-Q Quit", quitAction); StatusItem item2 = new StatusBarView.StatusItem("F10 Status Bar"); statusBar.setItems(Arrays.asList(item1, item2)); diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/CatalogCommand.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/CatalogCommand.java index 888071f6..d4f3ab71 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/CatalogCommand.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/CatalogCommand.java @@ -18,6 +18,7 @@ package org.springframework.shell.samples.catalog; import java.util.List; import org.springframework.shell.command.annotation.Command; +import org.springframework.shell.component.view.TerminalUIBuilder; import org.springframework.shell.samples.catalog.scenario.Scenario; import org.springframework.shell.standard.AbstractShellComponent; import org.springframework.shell.style.ThemeResolver; @@ -31,16 +32,18 @@ import org.springframework.shell.style.ThemeResolver; public class CatalogCommand extends AbstractShellComponent { private final List scenarios; + private final TerminalUIBuilder terminalUIBuilder; private final ThemeResolver themeResolver; - public CatalogCommand(List scenarios, ThemeResolver themeResolver) { + public CatalogCommand(List scenarios, TerminalUIBuilder terminalUIBuilder, ThemeResolver themeResolver) { this.scenarios = scenarios; + this.terminalUIBuilder = terminalUIBuilder; this.themeResolver = themeResolver; } @Command(command = "catalog") public void catalog() { - Catalog catalog = new Catalog(getTerminal(), scenarios, themeResolver); + Catalog catalog = new Catalog(terminalUIBuilder, themeResolver, scenarios); catalog.run(); } } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/AbstractScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/AbstractScenario.java index caf31f25..451a4990 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/AbstractScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/AbstractScenario.java @@ -15,7 +15,8 @@ */ package org.springframework.shell.samples.catalog.scenario; -import org.springframework.shell.component.view.control.AbstractView; +import org.springframework.shell.component.view.TerminalUI; +import org.springframework.shell.component.view.control.View; import org.springframework.shell.component.view.control.ViewService; import org.springframework.shell.component.view.event.EventLoop; import org.springframework.shell.style.ThemeResolver; @@ -27,20 +28,12 @@ import org.springframework.shell.style.ThemeResolver; */ public abstract class AbstractScenario implements Scenario { + private TerminalUI ui; private ViewService viewService; private EventLoop eventloop; private ThemeResolver themeResolver; private String themeName; - @Override - public Scenario configure(ViewService viewService, EventLoop eventloop, ThemeResolver themeResolver, String themeName) { - this.viewService = viewService; - this.eventloop = eventloop; - this.themeResolver = themeResolver; - this.themeName = themeName; - return this; - } - protected ViewService getViewService() { return viewService; } @@ -57,9 +50,23 @@ public abstract class AbstractScenario implements Scenario { return themeName; } - protected void configure(AbstractView view) { - view.setEventLoop(getEventloop()); - view.setThemeResolver(getThemeResolver()); - view.setThemeName(getThemeName()); + protected TerminalUI getTerminalUI() { + return ui; + } + + @Override + public Scenario configure(TerminalUI ui) { + this.ui = ui; + this.themeName = ui.getThemeName(); + this.themeResolver = ui.getThemeResolver(); + this.eventloop = ui.getEventLoop(); + this.viewService = ui.getViewService(); + return this; + } + + protected void configure(View view) { + if (ui != null) { + ui.configure(view); + } } } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/Scenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/Scenario.java index 3b7e223d..57b22c7c 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/Scenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/Scenario.java @@ -15,10 +15,8 @@ */ package org.springframework.shell.samples.catalog.scenario; +import org.springframework.shell.component.view.TerminalUI; import org.springframework.shell.component.view.control.View; -import org.springframework.shell.component.view.control.ViewService; -import org.springframework.shell.component.view.event.EventLoop; -import org.springframework.shell.style.ThemeResolver; /** * {@link Scenario} participates in a catalog showcase. @@ -44,11 +42,9 @@ public interface Scenario { /** * Configure scenario. * - * @param eventloop eventloop for scenario - * @param themeResolver theme resolver for scenario - * @param themeName theme name for scenario + * @param ui the terminal ui * @return scenario for chaining */ - Scenario configure(ViewService viewService, EventLoop eventloop, ThemeResolver themeResolver, String themeName); + Scenario configure(TerminalUI ui); } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java index a7ab1432..177eafb5 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java @@ -30,7 +30,7 @@ public class CheckedListViewScenario extends AbstractScenario { @Override public View build() { ListView view = new ListView<>(ItemStyle.RADIO); - view.setEventLoop(getEventloop()); + configure(view); view.setItems(Arrays.asList("item1", "item2", "item3")); return view; } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java index 03fdc303..9ca1448d 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java @@ -31,7 +31,7 @@ public class LongListViewScenario extends AbstractScenario { @Override public View build() { ListView view = new ListView<>(); - view.setEventLoop(getEventloop()); + configure(view); List items = IntStream.of(20).mapToObj(i -> "item" + i).collect(Collectors.toList()); view.setItems(items); return view; diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java index 0c1cd152..98037d5c 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java @@ -30,7 +30,7 @@ public class RadioListViewScenario extends AbstractScenario { @Override public View build() { ListView view = new ListView<>(ItemStyle.CHECKED); - view.setEventLoop(getEventloop()); + configure(view); view.setItems(Arrays.asList("item1", "item2", "item3")); return view; } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/SimpleListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/SimpleListViewScenario.java index 98c03360..bce74404 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/SimpleListViewScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/SimpleListViewScenario.java @@ -29,7 +29,7 @@ public class SimpleListViewScenario extends AbstractScenario { @Override public View build() { ListView view = new ListView<>(); - view.setEventLoop(getEventloop()); + configure(view); view.setItems(Arrays.asList("item1", "item2")); return view; } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/DialogScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/DialogScenario.java index 88390b6d..b461e6db 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/DialogScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/DialogScenario.java @@ -51,7 +51,6 @@ public class DialogScenario extends AbstractScenario { }); DialogView dialog = new DialogView(content, button); configure(dialog); - dialog.setViewService(getViewService()); return dialog; } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/SimpleInputViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/SimpleInputViewScenario.java index 91ba7c3b..f7a4ba1c 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/SimpleInputViewScenario.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/other/SimpleInputViewScenario.java @@ -27,7 +27,7 @@ public class SimpleInputViewScenario extends AbstractScenario { @Override public View build() { InputView view = new InputView(); - view.setEventLoop(getEventloop()); + configure(view); view.setTitle("Input"); view.setShowBorder(true); return view;