# Pastebin XtCffbni #!/usr/bin/env raku use Selkie::UI; sub dark-theme(--> Selkie::Theme) { Theme( base => Style(:fg(0xEEEEEE), :bg(0x16162E) ), text => Style(:fg(0xEEEEEE) ), text-dim => Style(:fg(0x888899) ), text-highlight => Style(:fg(0xFFFFFF), :bold ), border => Style(:fg(0x444466) ), border-focused => Style(:fg(0x7AA2F7), :bold ), input => Style(:fg(0xEEEEEE), :bg(0x1A1A2E) ), input-focused => Style(:fg(0xFFFFFF), :bg(0x2A2A3E) ), input-placeholder => Style(:fg(0x606080), :italic ), scrollbar-track => Style(:fg(0x333344) ), scrollbar-thumb => Style(:fg(0x7AA2F7) ), divider => Style(:fg(0x444466) ), tab-active => Style(:fg(0xFFFFFF), :bg(0x7AA2F7), :bold), tab-inactive => Style(:fg(0x8888A0), :bg(0x16162E) ), ); } sub light-theme(--> Selkie::Theme) { Theme( base => Style(:fg(0x222222), :bg(0xF5F5F0) ), text => Style(:fg(0x222222) ), text-dim => Style(:fg(0x666666) ), text-highlight => Style(:fg(0x000000), :bold ), border => Style(:fg(0xCCCCCC) ), border-focused => Style(:fg(0x3366CC), :bold ), input => Style(:fg(0x222222), :bg(0xFFFFFF) ), input-focused => Style(:fg(0x000000), :bg(0xEEEEFF) ), input-placeholder => Style(:fg(0x999999), :italic ), scrollbar-track => Style(:fg(0xDDDDDD) ), scrollbar-thumb => Style(:fg(0x3366CC) ), divider => Style(:fg(0xCCCCCC) ), tab-active => Style(:fg(0xFFFFFF), :bg(0x3366CC), :bold), tab-inactive => Style(:fg(0x666688), :bg(0xF5F5F0) ), ); } selkie-ui Chat { has $.dark is status = True; # You souldn't need to name it, it will use the attr name by default has @.messages is status; # You souldn't need to name it, it will use the attr name by default method init(%) is handler { :db{ :messages[ { speaker => 'bot', text => 'Hello! Type something and I will echo it back, transformed.' }, ], } } method chat-send(% (:$text = '', |)) is handler { my $text = $text.trim; if $text.chars { my @messages = @!messages; @messages.push: %( :speaker, :$text ); @messages.push: %( :speaker, :text("Reversed: { $text.flip }") ); :db{ :@messages } } } method chat-clear(%) is handler { :db { :messages[] } } method theme-toggle(%) is handler { :db { :dark(!$!dark) } } method RENDER { App({ .on-key: 'ctrl+q', -> $ { .quit }; .on-key: 'ctrl+l', -> $ { .dispatch: 'chat/clear'; .toast: 'Cleared'; } .on-key: 'ctrl+t', -> $ { .dispatch: 'theme/toggle'; .toast: 'Theme toggled'; }; VBox({ Text( ' Echo Chat — Ctrl+Enter send, Ctrl+L clear, Ctrl+T theme, Ctrl+Q quit' ).size(1).style: :fg(0x7AA2F7), :bold; Border( "Chat", CardList.children: { for @!messages -> %m { my $is-bot = %m eq 'bot'; my %name-style = $is-bot ?? |(:fg(0x9ECE6A), :bold) !! |(:fg(0x7AA2F7), :bold); my %body-style = |(:fg(0xEEEEEE)); Border( RichText.new( Span("{%m}: ").style(%name-style), Span(%m).style: %body-style ); ); } .select-last; } ); Border( 'Compose', MultiLineInput('Type a message — Ctrl+Enter to send').max-lines(5).size: 1 ).size(3).on-submit.tap: -> $text { if $text.chars > 0 { .dispatch('chat/send', :$text); .clear; } }; }).name: "main"; .dispatch: 'app/init'; }).theme: { $!dark ?? dark-theme !! light-theme }; } } # --- Go ------------------------------------------------------------------- my $chat = Chat.new; $chat.tick; $chat.focus($input); $chat.run;