1212import java .io .IOException ;
1313import java .io .Reader ;
1414import java .util .concurrent .ThreadLocalRandom ;
15+ import java .util .function .Supplier ;
1516import org .codejive .twinkle .ansi .Ansi ;
1617import org .codejive .twinkle .ansi .Color ;
1718import org .codejive .twinkle .ansi .Style ;
18- import org .codejive .twinkle .ansi .util .AnsiTricks ;
19+ import org .codejive .twinkle .ansi .util .Printable ;
1920import org .codejive .twinkle .fluent .Fluent ;
2021import org .codejive .twinkle .screen .Buffer ;
2122import org .codejive .twinkle .screen .BufferStack ;
2223import org .codejive .twinkle .screen .io .PrintBufferWriter ;
2324import org .codejive .twinkle .screen .util .FrameCounter ;
25+ import org .codejive .twinkle .screen .util .Sized ;
2426import org .codejive .twinkle .shapes .Borders ;
2527import org .codejive .twinkle .terminal .Terminal ;
28+ import org .codejive .twinkle .text .Position ;
2629import org .codejive .twinkle .text .Size ;
2730import org .codejive .twinkle .text .Sizer ;
31+ import org .jspecify .annotations .NonNull ;
2832
2933class BouncingTwinkleDemo {
3034
3135 private static final BufferStack buffers = BufferStack .create ();
3236 private static final Buffer helpBuffer = Buffer .of (30 , 10 );
3337 private static volatile Size size ;
34- private static volatile Size textSize ;
35- private static volatile int minX , minY , maxX , maxY , textX , textY , dx , dy ;
38+ private static volatile Position textPos ;
39+ private static volatile int dx , dy ;
3640 private static volatile long currentSleep = 50 ;
3741 private static volatile Borders .LineStyle lineStyle = Borders .LineStyle .ASCII ;
3842 private static volatile Borders .CornerStyle cornerStyle = Borders .CornerStyle .ASCII ;
3943 private static final FrameCounter fps = new FrameCounter ();
44+ private static final FigletShape text = new FigletShape ("TWINKLE" );
4045
4146 private static final String URL = "https://github.com/codejive/twinkle" ;
4247
@@ -70,21 +75,13 @@ public static void main(String[] args) throws Exception {
7075 try (Terminal terminal = Terminal .getDefault ()) {
7176 size = terminal .size ();
7277
73- String text = AnsiTricks .blockify (Sizer .trim (FigletFont .convertOneLine ("TWINKLE" )));
74- textSize = Sizer .measure (text );
75-
7678 terminal .onResize (BouncingTwinkleDemo ::handleResize );
7779
7880 try {
7981 // Hide cursor and clear screen
8082 Fluent .of (terminal .writer ()).screen ().alternate ().hide ().screen ().clear ().done ();
8183
82- minX = 1 ;
83- minY = 1 ;
84- maxX = Math .max (minX , size .width () - textSize .width () - 1 );
85- maxY = Math .max (minY , size .height () - textSize .height () - 1 );
86- textX = Math .max (minX , Math .min ((size .width () - textSize .width ()) / 2 , maxX ));
87- textY = Math .max (minY , Math .min ((size .height () - textSize .height ()) / 2 , maxY ));
84+ textPos = size .center (text .size ());
8885 dx = 1 ;
8986 dy = 1 ;
9087
@@ -113,7 +110,7 @@ public static void main(String[] args) throws Exception {
113110 .markup ("{green}[ {blue}{ul}{$1}Twinkle{/}{/ul}{green} ]" , URL );
114111 f .at (size .width () - 12 , 0 )
115112 .markup ("{green}[ {+}{white}fps %s{-} ]" , Math .round (fps .average ()));
116- f .at (textX , textY ).color (textColor ).text (text ).done ();
113+ f .at (textPos ).color (textColor ).text (text ).done ();
117114
118115 // Write entire frame buffer to connection in one call
119116 terminal .writer ().write (Ansi .cursorHome () + buffers .toAnsi ());
@@ -170,11 +167,7 @@ private static void toggleHelp() {
170167 BufferStack .BufferElement helpElement = buffers .contains (helpBuffer );
171168 if (helpElement == null ) {
172169 helpElement =
173- buffers .add (
174- helpBuffer ,
175- size .width () / 2 - helpBuffer .size ().width () / 2 ,
176- size .height () / 2 - helpBuffer .size ().height () / 2 ,
177- Integer .MAX_VALUE );
170+ buffers .add (helpBuffer , size .center (helpBuffer .size ()), Integer .MAX_VALUE );
178171 helpElement .transparancy = "" ;
179172 } else {
180173 helpElement .visible = !helpElement .visible ;
@@ -204,30 +197,31 @@ private static void drawHelp() {
204197 }
205198
206199 private static void handleResize (Size newSize ) {
207- maxX = Math .max (minX , newSize .width () - textSize .width () - 1 );
208- maxY = Math .max (minY , newSize .height () - textSize .height () - 1 );
209200 size = newSize ;
201+ BufferStack .BufferElement helpElement = buffers .contains (helpBuffer );
202+ if (helpElement != null ) {
203+ helpElement .pos = size .center (helpBuffer .size ());
204+ }
210205 }
211206
212207 private static void bounce () {
213- textX = Math .max (minX , Math .min (textX , maxX ));
214- textY = Math .max (minY , Math .min (textY , maxY ));
215-
216- if (maxX > minX ) {
217- textX += dx ;
218- if (textX <= minX || textX >= maxX ) {
219- textX = Math .max (minX , Math .min (textX , maxX ));
220- dx = -dx ;
221- }
208+ Size bounceSize = size .shrink (text .size ()).shrink (1 , 1 );
209+ int textX = textPos .x ();
210+ int textY = textPos .y ();
211+
212+ textX += dx ;
213+ if (textX <= 1 || textX >= bounceSize .width ()) {
214+ textX = Math .max (1 , Math .min (textX , bounceSize .width ()));
215+ dx = -dx ;
222216 }
223217
224- if (maxY > minY ) {
225- textY += dy ;
226- if (textY <= minY || textY >= maxY ) {
227- textY = Math .max (minY , Math .min (textY , maxY ));
228- dy = -dy ;
229- }
218+ textY += dy ;
219+ if (textY <= 1 || textY >= bounceSize .height ()) {
220+ textY = Math .max (1 , Math .min (textY , bounceSize .height ()));
221+ dy = -dy ;
230222 }
223+
224+ textPos = Position .of (textX , textY );
231225 }
232226
233227 private static void colorize () {
@@ -236,4 +230,97 @@ private static void colorize() {
236230 textColor = textPalette [idx ];
237231 }
238232 }
233+
234+ static class FigletShape extends CachingSupplierShape {
235+ private String text ;
236+
237+ public FigletShape (String initialText ) {
238+ super (null );
239+ text (initialText );
240+ }
241+
242+ public void text (String text ) {
243+ this .text = text ;
244+ update ();
245+ }
246+
247+ protected String getUpdated () {
248+ try {
249+ return Sizer .trim (FigletFont .convertOneLine (text ));
250+ } catch (Exception e ) {
251+ return "???" ;
252+ }
253+ }
254+ }
255+
256+ static class CachingSupplierShape extends SupplierShape {
257+ protected volatile String contents ;
258+ protected volatile Size size ;
259+
260+ public CachingSupplierShape (Supplier <String > supplier ) {
261+ super (supplier );
262+ this .contents = null ;
263+ this .size = null ;
264+ }
265+
266+ public String get () {
267+ if (contents == null ) {
268+ contents = getUpdated ();
269+ size = null ;
270+ }
271+ return contents ;
272+ }
273+
274+ protected void update () {
275+ contents = null ;
276+ size = null ;
277+ }
278+
279+ @ Override
280+ public @ NonNull Size size () {
281+ if (size == null ) {
282+ size = Sizer .measure (get ());
283+ }
284+ return size ;
285+ }
286+ }
287+
288+ abstract static class SupplierShape implements Supplier <String >, Printable , Shape {
289+ protected final Supplier <String > supplier ;
290+
291+ public SupplierShape (Supplier <String > supplier ) {
292+ this .supplier = supplier ;
293+ }
294+
295+ public String get () {
296+ if (supplier == null ) {
297+ throw new IllegalStateException (
298+ "Internal error: either provide a Supplier or override get()" );
299+ }
300+ return getUpdated ();
301+ }
302+
303+ protected String getUpdated () {
304+ return supplier .get ();
305+ }
306+
307+ @ Override
308+ public @ NonNull Appendable toAnsi (
309+ @ NonNull Appendable appendable , @ NonNull Style currentStyle ) {
310+ try {
311+ return appendable .append (get ());
312+ } catch (IOException e ) {
313+ throw new RuntimeException (e );
314+ }
315+ }
316+ }
317+
318+ interface Shape extends Sized {
319+
320+ default @ NonNull Size size () {
321+ return Size .MAX ;
322+ }
323+
324+ default void resize (@ NonNull Size availableSize ) {}
325+ }
239326}
0 commit comments