simple style import with a subset of css
This commit is contained in:
parent
a390a8908c
commit
9fc1d90455
@ -6,6 +6,7 @@ import fifo;
|
||||
|
||||
import std::io;
|
||||
import std::core::string;
|
||||
import std::core::mem::allocator;
|
||||
|
||||
|
||||
// element ids are just long ints
|
||||
@ -74,25 +75,14 @@ const uint MAX_CMDS = 2048;
|
||||
const uint ROOT_ID = 1;
|
||||
const uint TEXT_MAX = 64;
|
||||
|
||||
// global style, similar to the css box model
|
||||
struct Style { // css box model
|
||||
Rect padding;
|
||||
Rect border;
|
||||
Rect margin;
|
||||
Color bgcolor; // background color
|
||||
Color fgcolor; // foreground color
|
||||
Color brcolor; // border color
|
||||
ushort radius;
|
||||
}
|
||||
|
||||
|
||||
struct Ctx {
|
||||
IdTree tree;
|
||||
ElemCache cache;
|
||||
CmdQueue cmd_queue;
|
||||
StyleMap styles;
|
||||
// total size in pixels of the context
|
||||
ushort width, height;
|
||||
Style style;
|
||||
Style* style; // TODO: rename to "active_style" or something
|
||||
Font font;
|
||||
SpriteAtlas sprite_atlas;
|
||||
|
||||
@ -206,16 +196,12 @@ fn void? Ctx.init(&ctx)
|
||||
ctx.cmd_queue.init(MAX_CMDS)!;
|
||||
defer catch { (void)ctx.cmd_queue.free(); }
|
||||
|
||||
ctx.styles.init(allocator::heap());
|
||||
defer catch { ctx.styles.free(); }
|
||||
|
||||
ctx.active_div = 0;
|
||||
|
||||
// TODO: add style config
|
||||
ctx.style.margin = {2, 2, 2, 2};
|
||||
ctx.style.border = {2, 2, 2, 2};
|
||||
ctx.style.padding = {1, 1, 1, 1};
|
||||
ctx.style.radius = 12;
|
||||
ctx.style.bgcolor = 0x282828ffu.to_rgba();
|
||||
ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba();
|
||||
ctx.style.brcolor = 0xd79921ffu.to_rgba();
|
||||
ctx.style = &DEFAULT_STYLE;
|
||||
}
|
||||
|
||||
fn void Ctx.free(&ctx)
|
||||
@ -225,6 +211,7 @@ fn void Ctx.free(&ctx)
|
||||
(void)ctx.cmd_queue.free();
|
||||
(void)ctx.font.free();
|
||||
(void)ctx.sprite_atlas.free();
|
||||
(void)ctx.styles.free();
|
||||
}
|
||||
|
||||
fn void? Ctx.frame_begin(&ctx)
|
||||
|
@ -203,6 +203,17 @@ macro Color uint.to_rgba(u)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
macro Color uint.@to_rgba($u)
|
||||
{
|
||||
return {
|
||||
.r = (char)(($u >> 24) & 0xff),
|
||||
.g = (char)(($u >> 16) & 0xff),
|
||||
.b = (char)(($u >> 8) & 0xff),
|
||||
.a = (char)(($u >> 0) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
macro uint Color.to_uint(c)
|
||||
{
|
||||
uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
||||
|
402
lib/ugui.c3l/src/ugui_style.c3
Normal file
402
lib/ugui.c3l/src/ugui_style.c3
Normal file
@ -0,0 +1,402 @@
|
||||
module ugui;
|
||||
|
||||
import std::collections::map;
|
||||
import std::core::mem::allocator;
|
||||
import std::io;
|
||||
|
||||
// global style, similar to the css box model
|
||||
struct Style { // css box model
|
||||
Rect padding;
|
||||
Rect border;
|
||||
Rect margin;
|
||||
Color bgcolor; // background color
|
||||
Color fgcolor; // foreground color
|
||||
Color brcolor; // border color
|
||||
ushort radius;
|
||||
}
|
||||
|
||||
const Style DEFAULT_STYLE = {
|
||||
.margin = {2, 2, 2, 2},
|
||||
.border = {2, 2, 2, 2},
|
||||
.padding = {1, 1, 1, 1},
|
||||
.bgcolor = 0x282828ffu.@to_rgba(),
|
||||
.fgcolor = 0xfbf1c7ffu.@to_rgba(),
|
||||
.brcolor = 0xd79921ffu.@to_rgba(),
|
||||
.radius = 12,
|
||||
};
|
||||
|
||||
// style is stored in a hashmap, each style has an Id that can be generated by a string or whatever
|
||||
alias StyleMap = map::HashMap{Id, Style};
|
||||
|
||||
// push or update a new style into the map
|
||||
fn void StyleMap.register_style(&map, Style* style, Id id)
|
||||
{
|
||||
if (style == null) return;
|
||||
map.set(id, *style);
|
||||
}
|
||||
|
||||
// get a style from the map, if the style is not found then use a default style.
|
||||
fn Style* StyleMap.get_style(&map, Id id)
|
||||
{
|
||||
Style*? s = map.get_ref(id);
|
||||
if (catch e = s) {
|
||||
return &DEFAULT_STYLE;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
fn int StyleMap.import_style_string(&map, String text)
|
||||
{
|
||||
Parser p;
|
||||
p.lex.text = text;
|
||||
int added;
|
||||
|
||||
while (p.parse_style() == true) {
|
||||
added++;
|
||||
map.register_style(&p.style, p.style_id);
|
||||
if (p.lex.peep_token().type == EOF) break;
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
fn int Ctx.import_style_from_string(&ctx, String text) => ctx.styles.import_style_string(text);
|
||||
fn int Ctx.import_style_from_file(&ctx, String path)
|
||||
{
|
||||
char[] text;
|
||||
usz size = file::get_size(path)!!;
|
||||
text = mem::new_array(char, size);
|
||||
file::load_buffer(path, text)!!;
|
||||
defer mem::free(text);
|
||||
|
||||
int added = ctx.import_style_from_string((String)text);
|
||||
return added;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Style can be serialized and deserialized with a subset of CSS
|
||||
* <style name> {
|
||||
* padding : left right top bottom;
|
||||
* border : left right top bottom;
|
||||
* margin : left right top bottoms;
|
||||
* bgcolor : #RRGGBBAA;
|
||||
* fgcolor : #RRGGBBAA;
|
||||
* brcolor : #RRGGBBAA;
|
||||
* radius : uint;
|
||||
* }
|
||||
* The field "style name" will be hashed and the hash used as the id int the style map.
|
||||
* Fields may be excluded, each excluded field is set to zero.
|
||||
* The default unit is pixels, but millimeters is also available. The parser function accepts a scale
|
||||
* factor that has to be obtained with the window manager functions.
|
||||
*/
|
||||
|
||||
module ugui::css;
|
||||
|
||||
import std::ascii;
|
||||
import std::io;
|
||||
|
||||
// CSS parser module
|
||||
|
||||
enum TokenType {
|
||||
INVALID,
|
||||
IDENTIFIER,
|
||||
PUNCT,
|
||||
NUMBER,
|
||||
COLOR,
|
||||
EOF,
|
||||
}
|
||||
|
||||
enum Unit {
|
||||
PIXELS,
|
||||
MILLIMETERS
|
||||
}
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
usz line, col, off;
|
||||
String text;
|
||||
union {
|
||||
struct {
|
||||
float value;
|
||||
Unit unit;
|
||||
}
|
||||
Color color;
|
||||
}
|
||||
}
|
||||
|
||||
fn short Token.to_px(&t, float mm_to_px)
|
||||
{
|
||||
if (t.type != NUMBER) {
|
||||
unreachable("WFT you cannot convert to pixels a non-number");
|
||||
}
|
||||
if (t.unit == PIXELS) return (short)(t.value);
|
||||
return (short)(t.value * mm_to_px);
|
||||
}
|
||||
|
||||
struct Lexer {
|
||||
String text;
|
||||
usz line, col, off;
|
||||
}
|
||||
|
||||
macro char Lexer.peep(&lex) => lex.text[lex.off];
|
||||
|
||||
fn char Lexer.advance(&lex)
|
||||
{
|
||||
if (lex.off >= lex.text.len) return '\0';
|
||||
|
||||
char c = lex.text[lex.off];
|
||||
if (c == '\n') {
|
||||
lex.col = 0;
|
||||
lex.line++;
|
||||
} else {
|
||||
lex.col++;
|
||||
}
|
||||
lex.off++;
|
||||
return c;
|
||||
}
|
||||
|
||||
fn Token Lexer.next_token(&lex)
|
||||
{
|
||||
Token t;
|
||||
t.type = INVALID;
|
||||
t.off = lex.off;
|
||||
t.col = lex.col;
|
||||
t.line = lex.line;
|
||||
|
||||
if (lex.off >= lex.text.len) {
|
||||
return {.type = EOF};
|
||||
}
|
||||
|
||||
// skip whitespace
|
||||
while (ascii::is_space_m(lex.peep())) {
|
||||
if (lex.advance() == 0) return {.type = EOF};
|
||||
if (lex.off >= lex.text.len) return {.type = EOF};
|
||||
}
|
||||
t.off = lex.off;
|
||||
|
||||
switch (true) {
|
||||
case ascii::is_punct_m(lex.peep()) && lex.peep() != '#': // punctuation
|
||||
t.type = PUNCT;
|
||||
t.text = lex.text[lex.off:1];
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
|
||||
case lex.peep() == '#': // color
|
||||
t.type = COLOR;
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
usz hex_start = t.off+1;
|
||||
while (ascii::is_alnum_m(lex.peep())) {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
if (lex.off - hex_start != 8) {
|
||||
io::eprintfn("CSS lexing error at %d:%d: the only suppported color format is #RRGGBBAA", t.line, t.col);
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
|
||||
char[10] hex_str = (char[])"0x";
|
||||
hex_str[2..] = lex.text[hex_start..lex.off-1];
|
||||
uint? color_hex = ((String)hex_str[..]).to_uint();
|
||||
if (catch color_hex) {
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
t.color = color_hex.to_rgba();
|
||||
|
||||
case ascii::is_alpha_m(lex.peep()): // identifier
|
||||
t.type = IDENTIFIER;
|
||||
while (ascii::is_alnum_m(lex.peep())) {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
t.text = lex.text[t.off..lex.off-1];
|
||||
|
||||
case ascii::is_digit_m(lex.peep()): // number
|
||||
t.type = NUMBER;
|
||||
t.unit = PIXELS;
|
||||
// find the end of the number
|
||||
usz end;
|
||||
while (ascii::is_alnum_m(lex.peep()) || lex.peep() == '+' || lex.peep() == '-' || lex.peep() == '.') {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
end = lex.off;
|
||||
if (end - t.off > 2) {
|
||||
if (lex.text[end-2:2] == "px") {
|
||||
t.unit = PIXELS;
|
||||
end -= 2;
|
||||
} else if (lex.text[end-2:2] == "mm") {
|
||||
t.unit = MILLIMETERS;
|
||||
end -= 2;
|
||||
} else if (lex.text[end-2:2] == "pt" || lex.text[end-2:2] == "em") {
|
||||
io::eprintn("units 'em' or 'pt' are not supported at the moment");
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String number_str = lex.text[t.off..end-1];
|
||||
float? value = number_str.to_float();
|
||||
if (catch value) { t.type = INVALID; break; }
|
||||
t.value = value;
|
||||
t.text = lex.text[t.off..lex.off-1];
|
||||
}
|
||||
|
||||
if (t.type == INVALID) {
|
||||
io::eprintfn("CSS Lexing ERROR at %d:%d: '%s' is not a valid token", t.line, t.col, lex.text[t.off..lex.off]);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
fn Token Lexer.peep_token(&lex)
|
||||
{
|
||||
Lexer start_state = *lex;
|
||||
Token t;
|
||||
t = lex.next_token();
|
||||
*lex = start_state;
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
struct Parser {
|
||||
Lexer lex;
|
||||
Style style;
|
||||
Id style_id;
|
||||
float mm_to_px;
|
||||
}
|
||||
|
||||
macro bool Parser.expect_text(&p, Token* t, TokenType type, String text)
|
||||
{
|
||||
*t = p.lex.next_token();
|
||||
if (t.type == type && t.text == text) {
|
||||
return true;
|
||||
}
|
||||
io::eprintfn("CSS parsing error at %d:%d: expected type:%s text:'%s' but got type:%s text:'%s'",
|
||||
t.line, t.col, type, text, t.type, t.text);
|
||||
return false;
|
||||
}
|
||||
|
||||
macro bool Parser.expect(&p, Token* t, TokenType type)
|
||||
{
|
||||
*t = p.lex.next_token();
|
||||
if (t.type == type) return true;
|
||||
io::eprintfn("CSS parsing error at %d:%d: expected %s but got %s", t.line, t.col, type, t.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn bool Parser.parse_style(&p)
|
||||
{
|
||||
Token t;
|
||||
|
||||
// style name
|
||||
if (p.expect(&t, IDENTIFIER) == false) return false;
|
||||
p.style_id = t.text.hash();
|
||||
|
||||
// style body
|
||||
if (p.expect_text(&t, PUNCT, "{") == false) return false;
|
||||
|
||||
while (true) {
|
||||
if (p.parse_property() == false) return false;
|
||||
t = p.lex.peep_token();
|
||||
if (t.type != IDENTIFIER) break;
|
||||
}
|
||||
|
||||
if (p.expect_text(&t, PUNCT, "}") == false) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool Parser.parse_property(&p)
|
||||
{
|
||||
Token t, prop;
|
||||
if (p.expect(&prop, IDENTIFIER) == false) return false;
|
||||
if (p.expect_text(&t, PUNCT, ":") == false) return false;
|
||||
|
||||
switch (prop.text) {
|
||||
case "padding":
|
||||
Rect padding;
|
||||
if (p.parse_size(&padding) == false) return false;
|
||||
p.style.padding = padding;
|
||||
|
||||
case "border":
|
||||
Rect border;
|
||||
if (p.parse_size(&border) == false) return false;
|
||||
p.style.border = border;
|
||||
|
||||
case "margin":
|
||||
Rect margin;
|
||||
if (p.parse_size(&margin) == false) return false;
|
||||
p.style.margin = margin;
|
||||
|
||||
case "bgcolor":
|
||||
Color bgcolor;
|
||||
if (p.parse_color(&bgcolor) == false) return false;
|
||||
p.style.bgcolor = bgcolor;
|
||||
|
||||
case "fgcolor":
|
||||
Color fgcolor;
|
||||
if (p.parse_color(&fgcolor) == false) return false;
|
||||
p.style.fgcolor = fgcolor;
|
||||
|
||||
case "brcolor":
|
||||
Color brcolor;
|
||||
if (p.parse_color(&brcolor) == false) return false;
|
||||
p.style.brcolor = brcolor;
|
||||
|
||||
case "radius":
|
||||
short r;
|
||||
if (p.parse_number(&r) == false) return false;
|
||||
if (r < 0) {
|
||||
io::eprintfn("CSS parsing error at %d:%d: radius must be a positive number, got %d", t.line, t.col, r);
|
||||
return false;
|
||||
}
|
||||
p.style.radius = (ushort)r;
|
||||
|
||||
default:
|
||||
io::eprintfn("CSS parsing error at %d:%d: '%s' is not a valid property", prop.line, prop.col, prop.text);
|
||||
return false;
|
||||
}
|
||||
if (p.expect_text(&t, PUNCT, ";") == false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool Parser.parse_number(&p, short* n)
|
||||
{
|
||||
Token t;
|
||||
if (p.expect(&t, NUMBER) == false) return false;
|
||||
*n = t.to_px(p.mm_to_px);
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: since '#' is punctuation this cannot be done in parsing but it has to be done in lexing
|
||||
fn bool Parser.parse_color(&p, Color* c)
|
||||
{
|
||||
Token t;
|
||||
if (p.expect(&t, COLOR) == false) return false;
|
||||
*c = t.color;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool Parser.parse_size(&p, Rect* r)
|
||||
{
|
||||
short x;
|
||||
Token t;
|
||||
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
|
||||
t = p.lex.peep_token();
|
||||
if (t.type == NUMBER) {
|
||||
// we got another number so we expect three more
|
||||
r.x = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.y = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.w = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.h = x;
|
||||
return true;
|
||||
} else if (t.type == PUNCT && t.text == ";") {
|
||||
// just one number, all dimensions are the same
|
||||
r.x = r.y = r.w = r.h = x;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -113,6 +113,15 @@ fn int main(String[] args)
|
||||
ren.create_pipeline("UGUI_PIPELINE_RECT", RECT);
|
||||
|
||||
|
||||
// TESTING CSS
|
||||
String stylesheet = `
|
||||
ciao {
|
||||
margin: 12 24px 12mm 3;
|
||||
brcolor: #ffff00ff;
|
||||
radius: 10;
|
||||
}`;
|
||||
io::printfn("imported %d styles", ui.import_style_from_string(stylesheet));
|
||||
|
||||
isz frame;
|
||||
double fps;
|
||||
bool toggle = true;
|
||||
|
Loading…
Reference in New Issue
Block a user