View Javadoc
1   package com.guinetik.hexafun.examples.tui;
2   
3   import java.util.function.Function;
4   import java.util.function.Predicate;
5   
6   /**
7    * A View is a pure function from State to String.
8    *
9    * <p>Views are the building blocks of functional TUIs. They:
10   * <ul>
11   *   <li>Take immutable state as input</li>
12   *   <li>Return a String representation</li>
13   *   <li>Have no side effects</li>
14   *   <li>Compose via {@link #andThen(View)}</li>
15   * </ul>
16   *
17   * <p>Example usage:
18   * <pre class="language-java">{@code
19   * View<AppState> header = state -> "=== " + state.title() + " ===\n";
20   * View<AppState> content = state -> state.items().stream()...;
21   * View<AppState> footer = state -> "\nTotal: " + state.count();
22   *
23   * // Compose views
24   * View<AppState> screen = header
25   *     .andThen(content)
26   *     .andThen(footer);
27   *
28   * // Render (single side effect)
29   * System.out.print(screen.apply(state));
30   * }</pre>
31   *
32   * @param <S> The state type this view renders
33   */
34  @FunctionalInterface
35  public interface View<S> extends Function<S, String> {
36      /**
37       * Compose this view with another, concatenating their outputs.
38       *
39       * @param next The view to append after this one
40       * @return A new view that renders both in sequence
41       */
42      default View<S> andThen(View<S> next) {
43          return state -> this.apply(state) + next.apply(state);
44      }
45  
46      /**
47       * Compose with a separator between views.
48       *
49       * @param separator String to insert between views
50       * @param next The view to append
51       * @return A new composed view
52       */
53      default View<S> andThen(String separator, View<S> next) {
54          return state -> this.apply(state) + separator + next.apply(state);
55      }
56  
57      /**
58       * Conditional view - renders one of two views based on predicate.
59       *
60       * @param condition Predicate to test against state
61       * @param ifTrue View to render if condition is true
62       * @param ifFalse View to render if condition is false
63       * @return A new conditional view
64       */
65      static <S> View<S> when(
66          Predicate<S> condition,
67          View<S> ifTrue,
68          View<S> ifFalse
69      ) {
70          return state ->
71              condition.test(state) ? ifTrue.apply(state) : ifFalse.apply(state);
72      }
73  
74      /**
75       * Conditional view with empty fallback.
76       *
77       * @param condition Predicate to test against state
78       * @param view View to render if condition is true
79       * @return A new conditional view (empty if false)
80       */
81      static <S> View<S> when(Predicate<S> condition, View<S> view) {
82          return when(condition, view, empty());
83      }
84  
85      /**
86       * Empty view - renders nothing.
87       *
88       * @return A view that returns empty string
89       */
90      static <S> View<S> empty() {
91          return state -> "";
92      }
93  
94      /**
95       * Literal view - always renders the same string.
96       *
97       * @param text The literal string to render
98       * @return A view that ignores state and returns the literal
99       */
100     static <S> View<S> of(String text) {
101         return state -> text;
102     }
103 
104     /**
105      * Newline view - renders a blank line.
106      *
107      * @return A view that returns a newline
108      */
109     static <S> View<S> newline() {
110         return state -> "\n";
111     }
112 
113     /**
114      * Create a view from a function.
115      *
116      * @param fn Function to convert to a View
117      * @return The function as a View
118      */
119     static <S> View<S> from(Function<S, String> fn) {
120         return fn::apply;
121     }
122 
123     /**
124      * Combine multiple views into one.
125      *
126      * @param views Views to combine
127      * @return A single view that renders all in sequence
128      */
129     @SafeVarargs
130     static <S> View<S> compose(View<S>... views) {
131         return state -> {
132             StringBuilder sb = new StringBuilder();
133             for (View<S> view : views) {
134                 sb.append(view.apply(state));
135             }
136             return sb.toString();
137         };
138     }
139 }