We now have a full map that's basically the same mapping system from Roguish. There's a bug right now where it needs you to move once to calc the light and it's not being centered, but it does work.
parent
55b67dcf5d
commit
d798d154ae
@ -0,0 +1,371 @@ |
||||
|
||||
#line 1 "ansi_parser.rl" |
||||
#include <fmt/core.h> |
||||
#include <string_view> |
||||
#include "dbc.hpp" |
||||
#include <SFML/Graphics.hpp> |
||||
#include "ansi_parser.hpp" |
||||
#include <iostream> |
||||
|
||||
using namespace fmt; |
||||
|
||||
|
||||
#line 122 "ansi_parser.rl" |
||||
|
||||
|
||||
|
||||
#line 13 "ansi_parser.cpp" |
||||
static const char _ansi_parser_actions[] = { |
||||
0, 1, 0, 1, 3, 1, 4, 1,
|
||||
5, 1, 6, 1, 7, 1, 8, 1,
|
||||
9, 1, 10, 1, 11, 1, 15, 1,
|
||||
16, 2, 1, 12, 2, 1, 13, 2,
|
||||
6, 7, 2, 16, 5, 3, 1, 14,
|
||||
2 |
||||
}; |
||||
|
||||
static const char _ansi_parser_key_offsets[] = { |
||||
0, 0, 1, 2, 11, 12, 14, 17,
|
||||
18, 22, 23, 27, 28, 29, 30, 31,
|
||||
33, 36, 38, 41, 43, 46, 47, 50,
|
||||
51, 52, 53, 54, 55 |
||||
}; |
||||
|
||||
static const int _ansi_parser_trans_keys[] = { |
||||
27, 91, 48, 49, 50, 51, 52, 55,
|
||||
57, 53, 54, 109, 48, 109, 34, 48,
|
||||
55, 109, 50, 52, 55, 109, 109, 49,
|
||||
56, 57, 109, 109, 59, 50, 59, 48,
|
||||
57, 59, 48, 57, 48, 57, 59, 48,
|
||||
57, 48, 57, 109, 48, 57, 109, 56,
|
||||
57, 109, 59, 50, 109, 109, 27, 27,
|
||||
0 |
||||
}; |
||||
|
||||
static const char _ansi_parser_single_lengths[] = { |
||||
0, 1, 1, 7, 1, 2, 3, 1,
|
||||
4, 1, 4, 1, 1, 1, 1, 0,
|
||||
1, 0, 1, 0, 1, 1, 3, 1,
|
||||
1, 1, 1, 1, 1 |
||||
}; |
||||
|
||||
static const char _ansi_parser_range_lengths[] = { |
||||
0, 0, 0, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0 |
||||
}; |
||||
|
||||
static const char _ansi_parser_index_offsets[] = { |
||||
0, 0, 2, 4, 13, 15, 18, 22,
|
||||
24, 29, 31, 36, 38, 40, 42, 44,
|
||||
46, 49, 51, 54, 56, 59, 61, 65,
|
||||
67, 69, 71, 73, 75 |
||||
}; |
||||
|
||||
static const char _ansi_parser_trans_targs[] = { |
||||
2, 1, 3, 0, 4, 5, 8, 10,
|
||||
22, 26, 6, 7, 0, 28, 0, 6,
|
||||
28, 0, 7, 7, 7, 0, 28, 0,
|
||||
7, 7, 9, 28, 0, 28, 0, 11,
|
||||
12, 21, 28, 0, 28, 0, 13, 0,
|
||||
14, 0, 15, 0, 16, 0, 17, 16,
|
||||
0, 18, 0, 19, 18, 0, 20, 0,
|
||||
28, 20, 0, 28, 0, 23, 25, 28,
|
||||
0, 24, 0, 14, 0, 28, 0, 28,
|
||||
0, 2, 1, 2, 1, 0 |
||||
}; |
||||
|
||||
static const char _ansi_parser_trans_actions[] = { |
||||
0, 7, 0, 0, 21, 21, 21, 21,
|
||||
21, 21, 21, 21, 0, 31, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 17, 0, 15, 0, 0,
|
||||
0, 0, 0, 0, 19, 0, 0, 0,
|
||||
3, 0, 0, 0, 1, 0, 25, 0,
|
||||
0, 1, 0, 28, 0, 0, 1, 0,
|
||||
37, 0, 0, 9, 0, 0, 0, 0,
|
||||
0, 0, 0, 5, 0, 11, 0, 13,
|
||||
0, 0, 7, 23, 34, 0 |
||||
}; |
||||
|
||||
static const char _ansi_parser_eof_actions[] = { |
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 23 |
||||
}; |
||||
|
||||
static const int ansi_parser_start = 27; |
||||
static const int ansi_parser_first_final = 27; |
||||
static const int ansi_parser_error = 0; |
||||
|
||||
static const int ansi_parser_en_main = 27; |
||||
|
||||
|
||||
#line 125 "ansi_parser.rl" |
||||
|
||||
#include <ftxui/screen/terminal.hpp> |
||||
|
||||
ANSIParser::ANSIParser(sf::Color default_fg, sf::Color default_bg) : |
||||
$default_fg(default_fg), |
||||
$default_bg(default_bg) |
||||
{ |
||||
} |
||||
|
||||
bool ANSIParser::parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb) { |
||||
const wchar_t *start = NULL; |
||||
int cs = 0; |
||||
unsigned int value = 0; |
||||
const wchar_t *p = codes.data(); |
||||
const wchar_t *pe = p + codes.size(); |
||||
const wchar_t *eof = pe; |
||||
sf::Color bgcolor($default_bg); |
||||
sf::Color color($default_fg); |
||||
sf::Color* target = &color; |
||||
|
||||
|
||||
#line 120 "ansi_parser.cpp" |
||||
{ |
||||
cs = ansi_parser_start; |
||||
} |
||||
|
||||
#line 146 "ansi_parser.rl" |
||||
|
||||
#line 123 "ansi_parser.cpp" |
||||
{ |
||||
int _klen; |
||||
unsigned int _trans; |
||||
const char *_acts; |
||||
unsigned int _nacts; |
||||
const int *_keys; |
||||
|
||||
if ( p == pe ) |
||||
goto _test_eof; |
||||
if ( cs == 0 ) |
||||
goto _out; |
||||
_resume: |
||||
_keys = _ansi_parser_trans_keys + _ansi_parser_key_offsets[cs]; |
||||
_trans = _ansi_parser_index_offsets[cs]; |
||||
|
||||
_klen = _ansi_parser_single_lengths[cs]; |
||||
if ( _klen > 0 ) { |
||||
const int *_lower = _keys; |
||||
const int *_mid; |
||||
const int *_upper = _keys + _klen - 1; |
||||
while (1) { |
||||
if ( _upper < _lower ) |
||||
break; |
||||
|
||||
_mid = _lower + ((_upper-_lower) >> 1); |
||||
if ( (*p) < *_mid ) |
||||
_upper = _mid - 1; |
||||
else if ( (*p) > *_mid ) |
||||
_lower = _mid + 1; |
||||
else { |
||||
_trans += (unsigned int)(_mid - _keys); |
||||
goto _match; |
||||
} |
||||
} |
||||
_keys += _klen; |
||||
_trans += _klen; |
||||
} |
||||
|
||||
_klen = _ansi_parser_range_lengths[cs]; |
||||
if ( _klen > 0 ) { |
||||
const int *_lower = _keys; |
||||
const int *_mid; |
||||
const int *_upper = _keys + (_klen<<1) - 2; |
||||
while (1) { |
||||
if ( _upper < _lower ) |
||||
break; |
||||
|
||||
_mid = _lower + (((_upper-_lower) >> 1) & ~1); |
||||
if ( (*p) < _mid[0] ) |
||||
_upper = _mid - 2; |
||||
else if ( (*p) > _mid[1] ) |
||||
_lower = _mid + 2; |
||||
else { |
||||
_trans += (unsigned int)((_mid - _keys)>>1); |
||||
goto _match; |
||||
} |
||||
} |
||||
_trans += _klen; |
||||
} |
||||
|
||||
_match: |
||||
cs = _ansi_parser_trans_targs[_trans]; |
||||
|
||||
if ( _ansi_parser_trans_actions[_trans] == 0 ) |
||||
goto _again; |
||||
|
||||
_acts = _ansi_parser_actions + _ansi_parser_trans_actions[_trans]; |
||||
_nacts = (unsigned int) *_acts++; |
||||
while ( _nacts-- > 0 ) |
||||
{ |
||||
switch ( *_acts++ ) |
||||
{ |
||||
case 0: |
||||
#line 14 "ansi_parser.rl" |
||||
{ |
||||
start = p; |
||||
} |
||||
break; |
||||
case 1: |
||||
#line 18 "ansi_parser.rl" |
||||
{ |
||||
value = 0; |
||||
size_t len = p - start; |
||||
dbc::check(start[0] != '-', "negative numbers not supported"); |
||||
|
||||
switch(len) { |
||||
case 10: value += (start[len-10] - '0') * 1000000000; [[fallthrough]]; |
||||
case 9: value += (start[len- 9] - '0') * 100000000; [[fallthrough]]; |
||||
case 8: value += (start[len- 8] - '0') * 10000000; [[fallthrough]]; |
||||
case 7: value += (start[len- 7] - '0') * 1000000; [[fallthrough]]; |
||||
case 6: value += (start[len- 6] - '0') * 100000; [[fallthrough]]; |
||||
case 5: value += (start[len- 5] - '0') * 10000; [[fallthrough]]; |
||||
case 4: value += (start[len- 4] - '0') * 1000; [[fallthrough]]; |
||||
case 3: value += (start[len- 3] - '0') * 100; [[fallthrough]]; |
||||
case 2: value += (start[len- 2] - '0') * 10; [[fallthrough]]; |
||||
case 1: value += (start[len- 1] - '0'); |
||||
break; |
||||
default: |
||||
dbc::sentinel("can't process > 10 digits"); |
||||
} |
||||
} |
||||
break; |
||||
case 2: |
||||
#line 40 "ansi_parser.rl" |
||||
{ |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 3: |
||||
#line 43 "ansi_parser.rl" |
||||
{ |
||||
target = &color; |
||||
} |
||||
break; |
||||
case 4: |
||||
#line 46 "ansi_parser.rl" |
||||
{ |
||||
target = &bgcolor; |
||||
} |
||||
break; |
||||
case 5: |
||||
#line 50 "ansi_parser.rl" |
||||
{ |
||||
write_cb((*p)); |
||||
} |
||||
break; |
||||
case 6: |
||||
#line 54 "ansi_parser.rl" |
||||
{ |
||||
color = $default_fg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 7: |
||||
#line 58 "ansi_parser.rl" |
||||
{ |
||||
bgcolor = $default_bg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 8: |
||||
#line 62 "ansi_parser.rl" |
||||
{ |
||||
color = $default_bg; |
||||
bgcolor = $default_fg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 9: |
||||
#line 67 "ansi_parser.rl" |
||||
{ |
||||
color = $default_fg; |
||||
bgcolor = $default_bg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 10: |
||||
#line 72 "ansi_parser.rl" |
||||
{ |
||||
color = sf::Color(100,100,100); |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 11: |
||||
#line 76 "ansi_parser.rl" |
||||
{ |
||||
color = sf::Color::Red; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
break; |
||||
case 12: |
||||
#line 81 "ansi_parser.rl" |
||||
{ target->r = value; } |
||||
break; |
||||
case 13: |
||||
#line 82 "ansi_parser.rl" |
||||
{ target->g = value; } |
||||
break; |
||||
case 14: |
||||
#line 83 "ansi_parser.rl" |
||||
{ target->b = value; } |
||||
break; |
||||
case 15: |
||||
#line 84 "ansi_parser.rl" |
||||
{ value = 0; } |
||||
break; |
||||
case 16: |
||||
#line 85 "ansi_parser.rl" |
||||
{} |
||||
break; |
||||
#line 296 "ansi_parser.cpp" |
||||
} |
||||
} |
||||
|
||||
_again: |
||||
if ( cs == 0 ) |
||||
goto _out; |
||||
if ( ++p != pe ) |
||||
goto _resume; |
||||
_test_eof: {} |
||||
if ( p == eof ) |
||||
{ |
||||
const char *__acts = _ansi_parser_actions + _ansi_parser_eof_actions[cs]; |
||||
unsigned int __nacts = (unsigned int) *__acts++; |
||||
while ( __nacts-- > 0 ) { |
||||
switch ( *__acts++ ) { |
||||
case 16: |
||||
#line 85 "ansi_parser.rl" |
||||
{} |
||||
break; |
||||
#line 314 "ansi_parser.cpp" |
||||
} |
||||
} |
||||
} |
||||
|
||||
_out: {} |
||||
} |
||||
|
||||
#line 147 "ansi_parser.rl" |
||||
|
||||
bool good = pe - p == 0; |
||||
|
||||
if(!good) { |
||||
p -= 10; |
||||
// dear cthuhlu, save me from the pain that is wstring
|
||||
for(int i = 0; i < 100; i++) { |
||||
try { |
||||
print("{}", p[i] == 0x1B ? '^' : char(p[i])); |
||||
} catch(...) { |
||||
print("?=", int(p[i])); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return good; |
||||
} |
@ -0,0 +1,23 @@ |
||||
#pragma once |
||||
#include <string_view> |
||||
#include <SFML/Graphics.hpp> |
||||
#include <codecvt> |
||||
#include <functional> |
||||
|
||||
typedef std::function<void(sf::Color bgcolor, sf::Color color)> ColorCB; |
||||
|
||||
typedef std::function<void(wchar_t ch)> WriteCB; |
||||
|
||||
class ANSIParser { |
||||
sf::Color $default_fg; |
||||
sf::Color $default_bg; |
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; |
||||
|
||||
public: |
||||
ANSIParser(sf::Color default_fg, sf::Color default_bg); |
||||
|
||||
// disable copying
|
||||
ANSIParser(ANSIParser& ap) = delete; |
||||
|
||||
bool parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb); |
||||
}; |
@ -0,0 +1,163 @@ |
||||
#include <fmt/core.h> |
||||
#include <string_view> |
||||
#include "dbc.hpp" |
||||
#include <SFML/Graphics.hpp> |
||||
#include "ansi_parser.hpp" |
||||
#include <iostream> |
||||
|
||||
using namespace fmt; |
||||
|
||||
%%{ |
||||
machine ansi_parser; |
||||
alphtype int; |
||||
|
||||
action tstart { |
||||
start = fpc; |
||||
} |
||||
|
||||
action number { |
||||
value = 0; |
||||
size_t len = fpc - start; |
||||
dbc::check(start[0] != '-', "negative numbers not supported"); |
||||
|
||||
switch(len) { |
||||
case 10: value += (start[len-10] - '0') * 1000000000; [[fallthrough]]; |
||||
case 9: value += (start[len- 9] - '0') * 100000000; [[fallthrough]]; |
||||
case 8: value += (start[len- 8] - '0') * 10000000; [[fallthrough]]; |
||||
case 7: value += (start[len- 7] - '0') * 1000000; [[fallthrough]]; |
||||
case 6: value += (start[len- 6] - '0') * 100000; [[fallthrough]]; |
||||
case 5: value += (start[len- 5] - '0') * 10000; [[fallthrough]]; |
||||
case 4: value += (start[len- 4] - '0') * 1000; [[fallthrough]]; |
||||
case 3: value += (start[len- 3] - '0') * 100; [[fallthrough]]; |
||||
case 2: value += (start[len- 2] - '0') * 10; [[fallthrough]]; |
||||
case 1: value += (start[len- 1] - '0'); |
||||
break; |
||||
default: |
||||
dbc::sentinel("can't process > 10 digits"); |
||||
} |
||||
} |
||||
|
||||
action color_out { |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action is_fg { |
||||
target = &color; |
||||
} |
||||
action is_bg { |
||||
target = &bgcolor; |
||||
} |
||||
|
||||
action out { |
||||
write_cb(fc); |
||||
} |
||||
|
||||
action reset_fg { |
||||
color = $default_fg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action reset_bg { |
||||
bgcolor = $default_bg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action invert { |
||||
color = $default_bg; |
||||
bgcolor = $default_fg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action reset_invert { |
||||
color = $default_fg; |
||||
bgcolor = $default_bg; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action half_bright { |
||||
color = sf::Color(100,100,100); |
||||
color_cb(color, bgcolor); |
||||
} |
||||
action red_text { |
||||
color = sf::Color::Red; |
||||
color_cb(color, bgcolor); |
||||
} |
||||
|
||||
action red { target->r = value; } |
||||
action blue { target->g = value; } |
||||
action green { target->b = value; } |
||||
action start { value = 0; } |
||||
action end {} |
||||
action log { println("command {}", (char)fc); } |
||||
|
||||
ESC = 0x1B; |
||||
start = ESC "["; |
||||
fg = "38;" %is_fg; |
||||
bg = "48;" %is_bg; |
||||
reset = ("39" %reset_fg | "49" %reset_bg); |
||||
num = digit+ >tstart %number; |
||||
color256 = "5;"; |
||||
color24b = "2;"; |
||||
|
||||
ansi = ( |
||||
start %start |
||||
( |
||||
reset | |
||||
"0" %reset_fg %reset_bg | |
||||
"1" | |
||||
"2" %half_bright | |
||||
"3" | |
||||
"4" | |
||||
"5" | |
||||
"6" | |
||||
"7" %invert | |
||||
"31" %red_text | |
||||
"22" | |
||||
"24" | |
||||
"27" %reset_invert | |
||||
"9" ["0"-"7"] | |
||||
"10" ["0"-"7"] | |
||||
(fg|bg) (color24b num %red ";" num %blue ";" num %green ) %color_out |
||||
) "m" %end |
||||
); |
||||
|
||||
other = (any+ @out -- ESC)*; |
||||
|
||||
main := (other :> ansi)**; |
||||
}%% |
||||
|
||||
%% write data; |
||||
|
||||
#include <ftxui/screen/terminal.hpp> |
||||
|
||||
ANSIParser::ANSIParser(sf::Color default_fg, sf::Color default_bg) : |
||||
$default_fg(default_fg), |
||||
$default_bg(default_bg) |
||||
{ |
||||
} |
||||
|
||||
bool ANSIParser::parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb) { |
||||
const wchar_t *start = NULL; |
||||
int cs = 0; |
||||
unsigned int value = 0; |
||||
const wchar_t *p = codes.data(); |
||||
const wchar_t *pe = p + codes.size(); |
||||
const wchar_t *eof = pe; |
||||
sf::Color bgcolor($default_bg); |
||||
sf::Color color($default_fg); |
||||
sf::Color* target = &color; |
||||
|
||||
%% write init; |
||||
%% write exec; |
||||
|
||||
bool good = pe - p == 0; |
||||
|
||||
if(!good) { |
||||
p -= 10; |
||||
// dear cthuhlu, save me from the pain that is wstring |
||||
for(int i = 0; i < 100; i++) { |
||||
try { |
||||
print("{}", p[i] == 0x1B ? '^' : char(p[i])); |
||||
} catch(...) { |
||||
print("?=", int(p[i])); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return good; |
||||
} |
@ -0,0 +1,14 @@ |
||||
#pragma once |
||||
|
||||
namespace ColorValue { |
||||
const sf::Color BLACK{1, 4, 2}; |
||||
const sf::Color DARK_DARK{9, 29, 16}; |
||||
const sf::Color DARK_MID{14, 50, 26}; |
||||
const sf::Color DARK_LIGHT{0, 109, 44}; |
||||
const sf::Color MID{63, 171, 92}; |
||||
const sf::Color LIGHT_DARK{161, 217, 155}; |
||||
const sf::Color LIGHT_MID{199, 233, 192}; |
||||
const sf::Color LIGHT_LIGHT{229, 245, 224}; |
||||
const sf::Color WHITE{255, 255, 255}; |
||||
const sf::Color TRANSPARENT = sf::Color::Transparent; |
||||
} |
@ -0,0 +1,64 @@ |
||||
#include "panel.hpp" |
||||
#include "dbc.hpp" |
||||
|
||||
void Panel::resize(int w, int h) { |
||||
$dirty = true; |
||||
width = w; |
||||
height = h; |
||||
$screen = Screen(width, height); |
||||
} |
||||
|
||||
void Panel::set_renderer(Component renderer) { |
||||
$dirty = true; |
||||
$component = renderer; |
||||
} |
||||
|
||||
void Panel::add(Component child) { |
||||
dbc::pre("must set_renderer first", $component != nullptr); |
||||
$dirty = true; |
||||
$component->Add(child); |
||||
} |
||||
|
||||
void Panel::render() { |
||||
$dirty = true; |
||||
if(must_clear) $screen.Clear(); |
||||
Render($screen, $component->Render()); |
||||
} |
||||
|
||||
const std::wstring& Panel::to_string() { |
||||
if($dirty) { |
||||
std::string as_text = $screen.ToString(); |
||||
$screenout = $converter.from_bytes(as_text); |
||||
$dirty = false; |
||||
} |
||||
|
||||
return $screenout; |
||||
} |
||||
|
||||
void Panel::mouse_click(ftxui::Mouse::Button btn, Point pos) { |
||||
ftxui::Mouse mev{ |
||||
.button=btn, |
||||
.motion=ftxui::Mouse::Motion::Pressed, |
||||
.x=int(pos.x), .y=int(pos.y) |
||||
}; |
||||
|
||||
$component->OnEvent(ftxui::Event::Mouse("", mev)); |
||||
} |
||||
|
||||
void Panel::mouse_release(ftxui::Mouse::Button btn, Point pos) { |
||||
ftxui::Mouse mev{ |
||||
.button=btn, |
||||
.motion=ftxui::Mouse::Motion::Released, |
||||
.x=int(pos.x), .y=int(pos.y) |
||||
}; |
||||
|
||||
$component->OnEvent(ftxui::Event::Mouse("", mev)); |
||||
} |
||||
|
||||
const Screen& Panel::screen() { |
||||
return $screen; |
||||
} |
||||
|
||||
void Panel::key_press(ftxui::Event event) { |
||||
$component->OnEvent(event); |
||||
} |
@ -0,0 +1,60 @@ |
||||
#pragma once |
||||
#include <ftxui/dom/node.hpp> // for Render |
||||
#include <ftxui/component/component.hpp> |
||||
#include <ftxui/component/mouse.hpp> |
||||
#include <ftxui/dom/canvas.hpp> |
||||
#include <ftxui/screen/screen.hpp> |
||||
#include <ftxui/dom/canvas.hpp> |
||||
#include <ftxui/screen/screen.hpp> |
||||
#include <ftxui/dom/canvas.hpp> |
||||
#include <SFML/Graphics/Color.hpp> |
||||
#include <locale> |
||||
#include <codecvt> |
||||
#include "color.hpp" |
||||
#include "point.hpp" |
||||
|
||||
const int UI_PANEL_BORDER_PX=5; |
||||
|
||||
using ftxui::Renderer, ftxui::Component, ftxui::Element, ftxui::Screen; |
||||
|
||||
class Panel { |
||||
public: |
||||
int x; |
||||
int y; |
||||
int width; |
||||
int height; |
||||
bool has_border = false; |
||||
bool must_clear = true; |
||||
bool grid = false; |
||||
sf::Color default_bg = ColorValue::BLACK; |
||||
sf::Color default_fg = ColorValue::LIGHT_LIGHT; |
||||
sf::Color border_color = ColorValue::MID; |
||||
int border_px = UI_PANEL_BORDER_PX; |
||||
|
||||
bool $dirty = true; |
||||
Component $component = nullptr; |
||||
Screen $screen; |
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; |
||||
std::wstring $screenout; |
||||
|
||||
Panel(int x, int y, int width, int height, bool is_grid=false) : |
||||
x(x), |
||||
y(y), |
||||
width(width), |
||||
height(height), |
||||
grid(is_grid), |
||||
$screen(Screen(width, height)) |
||||
{ |
||||
must_clear = !is_grid; |
||||
}; |
||||
|
||||
void resize(int width, int height); |
||||
void set_renderer(Component renderer); |
||||
void add(Component child); |
||||
void render(); |
||||
void mouse_click(ftxui::Mouse::Button btn, Point pos); |
||||
void mouse_release(ftxui::Mouse::Button btn, Point pos); |
||||
void key_press(ftxui::Event event); |
||||
const std::wstring &to_string(); |
||||
const Screen &screen(); |
||||
}; |
@ -0,0 +1,275 @@ |
||||
#include "render.hpp" |
||||
#include "ansi_parser.hpp" |
||||
#include <cmath> |
||||
#include <fmt/core.h> |
||||
#include <array> |
||||
#include "map.hpp" |
||||
#include <iostream> |
||||
#include "color.hpp" |
||||
|
||||
#if defined(_WIN64) || defined(_WIN32) |
||||
#include <stdio.h> |
||||
#include <fcntl.h> |
||||
#include <io.h> |
||||
#endif |
||||
|
||||
using namespace fmt; |
||||
|
||||
SFMLRender::SFMLRender(sf::RenderWindow &window) : |
||||
$window(window), |
||||
$map_font_size(0), |
||||
$line_spacing(0), |
||||
$default_fg(ColorValue::LIGHT_MID), |
||||
$default_bg(ColorValue::BLACK), |
||||
$bg_sprite($font_texture), |
||||
$font(FONT_FILE_NAME), |
||||
$ui_text($font), |
||||
$ansi($default_fg, $default_bg) |
||||
{ |
||||
// force true color, but maybe I want to support different color sets
|
||||
$font.setSmooth(false); |
||||
$ui_text.setPosition({0,0}); |
||||
$ui_text.setCharacterSize($config.ui_font_size); |
||||
$ui_text.setFillColor(ColorValue::LIGHT_MID); |
||||
sf::Glyph glyph = $font.getGlyph($config.ui_base_char, $config.ui_font_size, false); |
||||
$text_bounds = glyph.bounds; |
||||
$cells_w = std::ceil($config.video_x / $text_bounds.size.x); |
||||
$cells_h = std::ceil($config.video_y / $text_bounds.size.y); |
||||
} |
||||
|
||||
sf::Sprite &SFMLRender::get_text_sprite(wchar_t tile) { |
||||
if(!$sprites.contains(tile)) { |
||||
sf::Glyph glyph = $font.getGlyph(tile, $map_font_size, false); |
||||
// WARNING! we actually have to do this here because SFML caches
|
||||
// the glyphs on the font texture, so this gets loaded each time
|
||||
// we get a new glyph from the font.
|
||||
$font_texture = $font.getTexture($map_font_size); |
||||
$sprites.try_emplace(tile, $font_texture, glyph.textureRect); |
||||
} |
||||
|
||||
return $sprites.at(tile); |
||||
} |
||||
|
||||
void SFMLRender::clear_cache() { |
||||
$sprites.clear(); |
||||
bool good = $font.openFromFile(FONT_FILE_NAME); |
||||
dbc::check(good, "Failed to load the font."); |
||||
$font.setSmooth(false); |
||||
$ui_text.setFont($font); |
||||
} |
||||
|
||||
void SFMLRender::center_panel(Panel &panel) { |
||||
int cell_center_x = ($cells_w - panel.width) / 2; |
||||
int cell_center_y = ($cells_h - panel.height) / 2; |
||||
|
||||
panel.x = cell_center_x * $text_bounds.size.x; |
||||
panel.y = cell_center_y * $text_bounds.size.y; |
||||
} |
||||
|
||||
void SFMLRender::resize_grid(int new_size, Panel &panel_out) { |
||||
auto glyph = $font.getGlyph($config.bg_tile, new_size, false); |
||||
int view_x = std::ceil(($config.video_x - panel_out.x) / glyph.bounds.size.x); |
||||
int view_y = std::ceil(($config.video_y - panel_out.y) / glyph.bounds.size.y); |
||||
|
||||
// looks good, set 'em all
|
||||
$base_glyph = glyph; |
||||
$map_font_size = new_size; |
||||
$sprites.clear(); // need to reset the sprites for the new size
|
||||
$line_spacing = $font.getLineSpacing($map_font_size); |
||||
$bg_sprite = get_text_sprite($config.bg_tile); |
||||
$grid_bounds = $bg_sprite.getLocalBounds(); |
||||
panel_out.resize(view_x, view_y); |
||||
} |
||||
|
||||
inline void configure_tile(const sf::Sprite &sprite, sf::FloatRect &sp_bounds, sf::FloatRect grid_bounds, float &width_delta, float &height_delta) { |
||||
// BUG: I think I could create a struct that kept this info for all sprites loaded
|
||||
// should look into caching all this instead of calcing it each time
|
||||
sp_bounds = sprite.getLocalBounds(); |
||||
|
||||
// calculate where to center the sprite, but only if it's smaller
|
||||
width_delta = grid_bounds.size.x > sp_bounds.size.x ? (grid_bounds.size.x - sp_bounds.size.x) / 2 : 0; |
||||
height_delta = grid_bounds.size.y > sp_bounds.size.x ? (grid_bounds.size.y - sp_bounds.size.y) / 2 : 0; |
||||
} |
||||
|
||||
void SFMLRender::render_grid(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y) { |
||||
wchar_t last_tile = $config.bg_tile; |
||||
sf::FloatRect sp_bounds; |
||||
float width_delta = 0; |
||||
float height_delta = 0; |
||||
sf::Sprite &sprite = get_text_sprite(last_tile); |
||||
const float start_x = x; |
||||
sf::Color cur_fg = default_fg; |
||||
sf::Color cur_bg = default_bg; |
||||
|
||||
$ansi.parse(text, [&](auto fg, auto bg) { |
||||
cur_fg = fg; |
||||
cur_bg = bg; |
||||
}, |
||||
|
||||
[&](wchar_t tile) { |
||||
switch(tile) { |
||||
case '\r': break; // ignore it
|
||||
case '\n': { |
||||
// don't bother processing newlines, just skip
|
||||
y += $line_spacing; |
||||
x = start_x; |
||||
} |
||||
break; |
||||
default: { |
||||
// only get a new sprite if the tile changed
|
||||
if(last_tile != tile) { |
||||
sprite = get_text_sprite(tile); |
||||
configure_tile(sprite, sp_bounds, $grid_bounds, width_delta, height_delta); |
||||
last_tile = tile; // update last tile seen
|
||||
} |
||||
|
||||
sprite.setPosition({x+width_delta, y+height_delta}); |
||||
sprite.setColor(cur_fg); |
||||
|
||||
// only draw background char if it's different from default
|
||||
if(cur_bg != default_bg) { |
||||
$bg_sprite.setPosition({x, y}); |
||||
$bg_sprite.setColor(cur_bg); |
||||
$window.draw($bg_sprite); |
||||
} |
||||
|
||||
$window.draw(sprite); |
||||
// next cell
|
||||
x += $base_glyph.advance; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
inline sf::FloatRect draw_chunk(sf::RenderWindow& window, |
||||
sf::FloatRect text_bounds, sf::Text& text, sf::Color default_bg, |
||||
sf::Color bgcolor, int bg_box_offset, float x, float y, std::wstring &out) |
||||
{ |
||||
text.setString(out); |
||||
text.setPosition({x, y}); |
||||
// get a base character for the cell size
|
||||
sf::FloatRect bounds({x, y}, {text_bounds.size.x * out.size(), text_bounds.size.y}); |
||||
|
||||
if(default_bg != bgcolor) { |
||||
sf::RectangleShape backing(bounds.size); |
||||
backing.setFillColor(bgcolor); |
||||
backing.setPosition({bounds.position.x, bounds.position.y + bg_box_offset}); |
||||
window.draw(backing); |
||||
} |
||||
|
||||
window.draw(text); |
||||
out.clear(); |
||||
return bounds; |
||||
} |
||||
|
||||
void SFMLRender::render_text(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float start_x, float start_y) { |
||||
std::wstring out; |
||||
float x = start_x; |
||||
float y = start_y; |
||||
sf::Color cur_bg = default_bg; |
||||
|
||||
// start with the default_fg until it's changed
|
||||
$ui_text.setFillColor(default_fg); |
||||
|
||||
$ansi.parse(text, |
||||
[&](auto fg, auto bg) { |
||||
if(out.size() > 0 ) { |
||||
auto bounds = draw_chunk($window, |
||||
$text_bounds, $ui_text, |
||||
default_bg, cur_bg, $config.bg_box_offset, x, y, out); |
||||
x += bounds.size.x; |
||||
} |
||||
cur_bg = bg; |
||||
$ui_text.setFillColor(fg); |
||||
}, |
||||
[&](wchar_t tile) { |
||||
switch(tile) { |
||||
case '\r': break; // ignore it
|
||||
case '\n': { |
||||
sf::FloatRect bounds; |
||||
|
||||
if(out.size() > 0) { |
||||
bounds = draw_chunk($window, $text_bounds, |
||||
$ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out); |
||||
} else { |
||||
bounds = $ui_text.getLocalBounds(); |
||||
} |
||||
|
||||
y += bounds.size.y; |
||||
x = start_x; // reset to the original position
|
||||
} |
||||
break; |
||||
default: |
||||
out += tile; |
||||
break; |
||||
} |
||||
} |
||||
); |
||||
|
||||
if(out.size() > 0) { |
||||
draw_chunk($window, $text_bounds, $ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out); |
||||
} |
||||
} |
||||
|
||||
void SFMLRender::draw_sprite(sf::Sprite &sprite, sf::Shader *shader) { |
||||
$window.draw(sprite, shader); |
||||
} |
||||
|
||||
/*
|
||||
* Does not render the panel, you have to do that so you can control |
||||
* when things render. |
||||
*/ |
||||
void SFMLRender::draw(Panel &panel, float x_offset, float y_offset) { |
||||
const std::wstring &panelout = panel.to_string(); |
||||
|
||||
auto bounds = panel.grid ? $grid_bounds : $text_bounds; |
||||
|
||||
sf::RectangleShape backing( |
||||
sf::Vector2f(bounds.size.x * panel.width + panel.border_px, |
||||
bounds.size.y * panel.height + panel.border_px)); |
||||
|
||||
backing.setFillColor(panel.default_bg); |
||||
|
||||
if(panel.has_border) { |
||||
backing.setOutlineColor(panel.border_color); |
||||
backing.setOutlineThickness(panel.border_px); |
||||
} |
||||
|
||||
backing.setPosition({panel.x + x_offset, panel.y + y_offset}); |
||||
$window.draw(backing); |
||||
|
||||
if(panel.grid) { |
||||
render_grid(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset); |
||||
} else { |
||||
render_text(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset); |
||||
} |
||||
} |
||||
|
||||
bool SFMLRender::mouse_position(Panel &panel, Point &out) { |
||||
// yes, you have to do this in sfml
|
||||
sf::Vector2f pos = $window.mapPixelToCoords(sf::Mouse::getPosition($window)); |
||||
|
||||
auto bounds = panel.grid ? $grid_bounds : $text_bounds; |
||||
|
||||
if(pos.x >= panel.x && pos.y >= panel.y |
||||
&& pos.x <= (panel.x + panel.width * bounds.size.x) |
||||
&& pos.y <= (panel.y + panel.height * bounds.size.y)) |
||||
{ |
||||
out = { |
||||
size_t((pos.x - panel.x) / bounds.size.x), |
||||
size_t((pos.y - panel.y) / bounds.size.y) |
||||
}; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
void SFMLRender::init_terminal() { |
||||
#if defined(_WIN64) || defined(_WIN32) |
||||
_setmode(_fileno(stdout), _O_U16TEXT); |
||||
#endif |
||||
|
||||
ftxui::Terminal::SetColorSupport(ftxui::Terminal::Color::TrueColor); |
||||
} |
@ -0,0 +1,80 @@ |
||||
#pragma once |
||||
|
||||
#include <ftxui/screen/screen.hpp> |
||||
#include <ftxui/dom/canvas.hpp> |
||||
#include <SFML/Window.hpp> |
||||
#include <SFML/System.hpp> |
||||
#include <SFML/Graphics.hpp> |
||||
#include <SFML/Graphics/Rect.hpp> |
||||
#include "point.hpp" |
||||
#include <codecvt> |
||||
#include "ansi_parser.hpp" |
||||
#include "panel.hpp" |
||||
#include "constants.hpp" |
||||
#include <optional> |
||||
|
||||
using ftxui::Canvas, ftxui::Screen; |
||||
|
||||
/*
|
||||
* BUG: This could be so much better. |
||||
*/ |
||||
struct RenderConfig { |
||||
unsigned int video_x = VIDEO_WINDOW_X; |
||||
unsigned int video_y = VIDEO_WINDOW_Y; |
||||
int ui_font_size=UI_FONT_SIZE; |
||||
int base_map_font_size=BASE_MAP_FONT_SIZE; |
||||
wchar_t bg_tile = BG_TILE; |
||||
wchar_t ui_base_char = UI_BASE_CHAR; |
||||
int bg_box_offset=BG_BOX_OFFSET; |
||||
}; |
||||
|
||||
struct SFMLRender { |
||||
int $cells_w = 0; |
||||
int $cells_h = 0; |
||||
RenderConfig $config; |
||||
sf::RenderWindow& $window; |
||||
int $map_font_size; |
||||
float $line_spacing; |
||||
sf::Color $default_fg; |
||||
sf::Color $default_bg; |
||||
sf::Texture $font_texture; |
||||
sf::Sprite $bg_sprite; |
||||
sf::Font $font; |
||||
sf::Text $ui_text; |
||||
ANSIParser $ansi; |
||||
|
||||
std::unordered_map<wchar_t, sf::Sprite> $sprites; |
||||
sf::Glyph $base_glyph; |
||||
sf::FloatRect $grid_bounds; |
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter; |
||||
sf::FloatRect $text_bounds; |
||||
|
||||
SFMLRender(sf::RenderWindow& window); |
||||
|
||||
// disable copy
|
||||
SFMLRender(SFMLRender &other) = delete; |
||||
|
||||
sf::Sprite &get_text_sprite(wchar_t tile); |
||||
void resize_grid(int new_size, Panel &panel_out); |
||||
void render_grid(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y); |
||||
void render_text(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y); |
||||
|
||||
void draw(Panel &panel, float x_offset=0.0f, float y_offset=0.0f); |
||||
void draw_sprite(sf::Sprite &sprite, sf::Shader *shader); |
||||
void center_panel(Panel &panel); |
||||
|
||||
std::optional<sf::Event> poll_event() { |
||||
return $window.pollEvent(); |
||||
} |
||||
|
||||
void close() { return $window.close(); } |
||||
|
||||
bool is_open() { return $window.isOpen(); } |
||||
|
||||
int font_size() { return $map_font_size; } |
||||
void clear() { $window.clear(); } |
||||
void display() { $window.display(); } |
||||
bool mouse_position(Panel &panel, Point &out); |
||||
void clear_cache(); |
||||
static void init_terminal(); |
||||
}; |
@ -0,0 +1,15 @@ |
||||
[wrap-file] |
||||
directory = FTXUI-5.0.0 |
||||
source_url = https://github.com/ArthurSonzogni/FTXUI/archive/refs/tags/v5.0.0.tar.gz |
||||
source_filename = FTXUI-5.0.0.tar.gz |
||||
source_hash = a2991cb222c944aee14397965d9f6b050245da849d8c5da7c72d112de2786b5b |
||||
patch_filename = ftxui_5.0.0-1_patch.zip |
||||
patch_url = https://wrapdb.mesonbuild.com/v2/ftxui_5.0.0-1/get_patch |
||||
patch_hash = 21c654e82739b90b95bd98c1d321264608d37c50d29fbcc3487f790fd5412909 |
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/ftxui_5.0.0-1/FTXUI-5.0.0.tar.gz |
||||
wrapdb_version = 5.0.0-1 |
||||
|
||||
[provide] |
||||
ftxui-screen = screen_dep |
||||
ftxui-dom = dom_dep |
||||
ftxui-component = component_dep |
Loading…
Reference in new issue