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 }