View.java
package com.guinetik.hexafun.examples.tui;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* A View is a pure function from State to String.
*
* <p>Views are the building blocks of functional TUIs. They:
* <ul>
* <li>Take immutable state as input</li>
* <li>Return a String representation</li>
* <li>Have no side effects</li>
* <li>Compose via {@link #andThen(View)}</li>
* </ul>
*
* <p>Example usage:
* <pre class="language-java">{@code
* View<AppState> header = state -> "=== " + state.title() + " ===\n";
* View<AppState> content = state -> state.items().stream()...;
* View<AppState> footer = state -> "\nTotal: " + state.count();
*
* // Compose views
* View<AppState> screen = header
* .andThen(content)
* .andThen(footer);
*
* // Render (single side effect)
* System.out.print(screen.apply(state));
* }</pre>
*
* @param <S> The state type this view renders
*/
@FunctionalInterface
public interface View<S> extends Function<S, String> {
/**
* Compose this view with another, concatenating their outputs.
*
* @param next The view to append after this one
* @return A new view that renders both in sequence
*/
default View<S> andThen(View<S> next) {
return state -> this.apply(state) + next.apply(state);
}
/**
* Compose with a separator between views.
*
* @param separator String to insert between views
* @param next The view to append
* @return A new composed view
*/
default View<S> andThen(String separator, View<S> next) {
return state -> this.apply(state) + separator + next.apply(state);
}
/**
* Conditional view - renders one of two views based on predicate.
*
* @param condition Predicate to test against state
* @param ifTrue View to render if condition is true
* @param ifFalse View to render if condition is false
* @return A new conditional view
*/
static <S> View<S> when(
Predicate<S> condition,
View<S> ifTrue,
View<S> ifFalse
) {
return state ->
condition.test(state) ? ifTrue.apply(state) : ifFalse.apply(state);
}
/**
* Conditional view with empty fallback.
*
* @param condition Predicate to test against state
* @param view View to render if condition is true
* @return A new conditional view (empty if false)
*/
static <S> View<S> when(Predicate<S> condition, View<S> view) {
return when(condition, view, empty());
}
/**
* Empty view - renders nothing.
*
* @return A view that returns empty string
*/
static <S> View<S> empty() {
return state -> "";
}
/**
* Literal view - always renders the same string.
*
* @param text The literal string to render
* @return A view that ignores state and returns the literal
*/
static <S> View<S> of(String text) {
return state -> text;
}
/**
* Newline view - renders a blank line.
*
* @return A view that returns a newline
*/
static <S> View<S> newline() {
return state -> "\n";
}
/**
* Create a view from a function.
*
* @param fn Function to convert to a View
* @return The function as a View
*/
static <S> View<S> from(Function<S, String> fn) {
return fn::apply;
}
/**
* Combine multiple views into one.
*
* @param views Views to combine
* @return A single view that renders all in sequence
*/
@SafeVarargs
static <S> View<S> compose(View<S>... views) {
return state -> {
StringBuilder sb = new StringBuilder();
for (View<S> view : views) {
sb.append(view.apply(state));
}
return sb.toString();
};
}
}