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 7a8ef537..8d8c2eb4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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. @@ -43,7 +43,6 @@ import org.springframework.shell.component.view.control.MenuView.MenuItem; import org.springframework.shell.component.view.control.MenuView.MenuItemCheckStyle; import org.springframework.shell.component.view.control.StatusBarView; import org.springframework.shell.component.view.control.StatusBarView.StatusItem; -import org.springframework.shell.component.view.control.View; import org.springframework.shell.component.view.control.cell.AbstractListCell; import org.springframework.shell.component.view.event.EventLoop; import org.springframework.shell.component.view.event.KeyEvent; @@ -56,6 +55,7 @@ import org.springframework.shell.geom.HorizontalAlign; import org.springframework.shell.geom.Rectangle; import org.springframework.shell.geom.VerticalAlign; import org.springframework.shell.samples.catalog.scenario.Scenario; +import org.springframework.shell.samples.catalog.scenario.Scenario.ScenarioContext; import org.springframework.shell.samples.catalog.scenario.ScenarioComponent; import org.springframework.shell.style.ThemeResolver; import org.springframework.util.ObjectUtils; @@ -78,7 +78,7 @@ public class Catalog { // mapping from category name to scenarios(can belong to multiple categories) private final Map> categoryMap = new TreeMap<>(); - private View currentScenarioView = null; + private ScenarioContext currentScenarioContext = null; private TerminalUI ui; private ListView categories; private ListView scenarios; @@ -130,8 +130,9 @@ public class Catalog { eventLoop.onDestroy(eventLoop.keyEvents() .doOnNext(m -> { if (m.getPlainKey() == Key.q && m.hasCtrl()) { - if (currentScenarioView != null) { - currentScenarioView = null; + if (currentScenarioContext != null) { + currentScenarioContext.stop(); + currentScenarioContext = null; ui.setRoot(app, true); ui.setFocus(categories); } @@ -172,13 +173,11 @@ public class Catalog { // handle event when scenario is chosen eventLoop.onDestroy(eventLoop.viewEvents(LISTVIEW_SCENARIO_TYPEREF, scenarios) .subscribe(event -> { - 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; + ScenarioContext context = event.args().item().scenario().configure(ui).buildContext(); + ui.configure(context.view()); + component.setRoot(context.view(), true); + context.start(); + currentScenarioContext = context; })); 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 451a4990..2df704db 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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. @@ -54,6 +54,11 @@ public abstract class AbstractScenario implements Scenario { return ui; } + @Override + public View build() { + throw new UnsupportedOperationException("Need to implement via 'buildContext'"); + } + @Override public Scenario configure(TerminalUI ui) { this.ui = ui; 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 57b22c7c..b600fa57 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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. @@ -15,8 +15,10 @@ */ package org.springframework.shell.samples.catalog.scenario; +import org.springframework.lang.Nullable; import org.springframework.shell.component.view.TerminalUI; import org.springframework.shell.component.view.control.View; +import org.springframework.shell.samples.catalog.Catalog; /** * {@link Scenario} participates in a catalog showcase. @@ -28,17 +30,29 @@ public interface Scenario { // Common category names public static final String CATEGORY_ALL = "All Scenarios"; public static final String CATEGORY_LISTVIEW = "ListView"; + public static final String CATEGORY_PROGRESSVIEW = "ProgressView"; public static final String CATEGORY_BOXVIEW = "BoxView"; public static final String CATEGORY_LAYOUT = "Layout"; public static final String CATEGORY_OTHER = "Other"; /** - * Build a {@link View} to be shown with a scenario. + * Build a {@link View} to be shown with a scenario. Prefer {@link #buildContext()} + * as this method will be deprecated and renamed. * * @return view of a scenario + * @see #buildContext() */ View build(); + /** + * Build a {@link ScenarioContext} wrapping needed view and lifecycle methods. + * + * @return a scenario context + */ + default ScenarioContext buildContext() { + return ScenarioContext.of(build()); + } + /** * Configure scenario. * @@ -47,4 +61,78 @@ public interface Scenario { */ Scenario configure(TerminalUI ui); + /** + * Context represents a build {@code View} and lifecycle methods called by a + * catalog app. + * + * For simple {@link View} without any need for lifecycle methods, just call + * {@link #of(View)}. + */ + public interface ScenarioContext { + + /** + * Get a view represented by a {@link Scenario}. + * + * @return a scenario view + */ + View view(); + + /** + * Called by a {@link Catalog} when its ready to show a {@link View} build from + * a {@link Scenario}. + */ + void start(); + + /** + * Called by a {@link Catalog} when its ready to discard a {@link View} build from + * a {@link Scenario}. + */ + void stop(); + + /** + * Utility method to build a {@code ScenarioContext} of {@link View} without + * lifecycle callbacks. + * + * @param view the main view + * @return a scenario context + */ + public static ScenarioContext of(View view) { + return of(view, null, null); + } + + /** + * Utility method to build a {@code ScenarioContext}. + * + * @param view the main view + * @param start the start callback + * @param stop the stop callback + * @return a scenario context + */ + public static ScenarioContext of(View view, @Nullable Runnable start, @Nullable Runnable stop) { + return new ScenarioContext() { + + @Override + public View view() { + return view; + } + + @Override + public void start() { + if (start != null) { + start.run(); + } + } + + @Override + public void stop() { + if (stop != null) { + stop.run(); + } + } + + }; + } + + } + }