ui elements + layout

This commit is contained in:
PS 2022-04-07 15:48:16 +02:00
parent d7c708e5d4
commit c9709a7eac
5 changed files with 198 additions and 55 deletions

View File

@ -155,6 +155,32 @@ ed_frame_prepare(App_State* state)
internal void internal void
ed_frame(App_State* state) ed_frame(App_State* state)
{ {
UI* ui = &state->editor->ui;
UI_Layout layout = {};
layout.bounds_min = v2{ 500, 200 };
layout.bounds_max = v2{ 700, 500 };
layout.row_height = ui->font_ascent + ui->font_descent + ui->font_line_gap + 15;
layout.row_gap = 2;
layout.col_gap = 2;
layout.at = layout.bounds_min;
ui->layout = &layout;
ui_text(ui, lit_str("Hi there!"));
show = ui_toggle(ui, lit_str("my toggle"), show);
if (show)
{
ui_layout_row_begin(ui, 2);
{
ui_button(ui, lit_str("Sup"));
ui_button(ui, lit_str("you there"));
}
ui_layout_row_end(ui);
}
ui_button(ui, lit_str("Hi there my good sir"));
edr_render_begin(state); edr_render_begin(state);
ui_draw(&state->editor->ui); ui_draw(&state->editor->ui);
edr_render(state); edr_render(state);

View File

@ -80,6 +80,7 @@ ui_create(u32 widget_pool_cap, u32 verts_cap, Input_State* input, Allocator* a)
}; };
ui_sprite_register(&result, (u8*)white_sprite, 4, 4, WHITE_SPRITE_ID); ui_sprite_register(&result, (u8*)white_sprite, 4, 4, WHITE_SPRITE_ID);
ui_create_default_style_sheet();
return result; return result;
} }
@ -166,7 +167,7 @@ ui_sprite_char_get_draw_cmd(UI* ui, v3 at, u32 codepoint)
}; };
result.pmin = at; result.pmin = at;
result.pmin.XY += sprite.draw_offset; result.pmin.XY += sprite.draw_offset;
result.pmin = v3_floor(result.pmin); result.pmin.XY = v2_floor(result.pmin.XY);
result.pmax = result.pmin + dim; result.pmax = result.pmin + dim;
result.baseline_after = v3{ result.pmax.x, at.y, at.z }; result.baseline_after = v3{ result.pmax.x, at.y, at.z };
@ -208,47 +209,6 @@ global bool show = false;
internal void internal void
ui_draw(UI* ui) ui_draw(UI* ui)
{ {
UI_Widget_Desc d0 = {
{
(UIWidgetStyle_Bg),
WHITE_V4,
BLACK_V4,
WHITE_SPRITE_ID,
},
lit_str("Hi there!"),
v2{ 32.0f, 32.0f },
v2{ 128.0f, 64.0f },
};
UI_Widget_Result r0 = ui_widget_push(ui, d0);
UI_Widget_Desc d1 = d0;
d1.style.flags |= UIWidgetStyle_Outline | UIWidgetStyle_MouseClick | UIWidgetStyle_Text;
d1.style.color_bg = PINK_V4;//{ 0.1f, 0.1f, 0.1f, 1.0f }; //
d1.style.color_fg = GREEN_V4;
d1.p_min = v2{ 512, 32 };
d1.p_max = v2{ 640, 128 };
d1.string = lit_str("Hello there my friend, what's going on?");
UI_Widget_Result r1 = ui_widget_push(ui, d1);
bool clicked_r1 = has_flag(r1.flags, UIWidgetResult_MouseLeft_WentUp);
if (clicked_r1) show = !show;
UI_Widget_Result r2 = {};
if (show)
{
UI_Widget_Desc d2 = d1;
d1.string = lit_str("Hello There");
d1.p_min = v2{ 560, 64 };
d1.p_max = v2{ 700, 256 };
r2 = ui_widget_push(ui, d1);
}
bool clicked_r2 = has_flag(r2.flags, UIWidgetResult_MouseLeft_WentUp);
assert(
(!clicked_r1 && !clicked_r2) ||
(clicked_r1 && !clicked_r2) ||
(!clicked_r1 && clicked_r2)
);
u32 widget_count = ui->widgets.free_len; u32 widget_count = ui->widgets.free_len;
r32 range_min = -10; r32 range_min = -10;
r32 range_max = -1; r32 range_max = -1;
@ -434,21 +394,22 @@ ui_widgets_to_geometry_recursive(UI* ui, UI_Widget* widget, r32 z_start, r32 z_s
if (has_flag(child->desc.style.flags, UIWidgetStyle_Outline)) if (has_flag(child->desc.style.flags, UIWidgetStyle_Outline))
{ {
ui_sprite_push(ui, bg_min, bg_max, WHITE_SPRITE_ID, color_fg); ui_sprite_push(ui, bg_min, bg_max, WHITE_SPRITE_ID, color_fg);
bg_min += v3{ 3, 3, 0}; z_at += z_step;
bg_max -= v3{ 3, 3, 0}; bg_min += v3{ 1, 1, 0};
bg_max -= v3{ 1, 1, 0};
} }
if (has_flag(child->desc.style.flags, UIWidgetStyle_Bg)) if (has_flag(child->desc.style.flags, UIWidgetStyle_Bg))
{ {
z_at += z_step;
bg_min.z = z_at; bg_min.z = z_at;
bg_max.z = z_at; bg_max.z = z_at;
ui_sprite_push(ui, bg_min, bg_max, desc.style.sprite, color_bg); ui_sprite_push(ui, bg_min, bg_max, desc.style.sprite, color_bg);
z_at += z_step;
} }
if (has_flag(child->desc.style.flags, UIWidgetStyle_Text)) if (has_flag(child->desc.style.flags, UIWidgetStyle_TextWrap | UIWidgetStyle_TextClip))
{ {
z_at += z_step + z_step;
r32 space_width = ui->font_space_width; r32 space_width = ui->font_space_width;
r32 to_baseline = ui->font_line_gap + ui->font_ascent; r32 to_baseline = ui->font_line_gap + ui->font_ascent;
v3 line_offset = { 5, 3 + to_baseline, 0 }; v3 line_offset = { 5, 3 + to_baseline, 0 };
@ -471,6 +432,8 @@ ui_widgets_to_geometry_recursive(UI* ui, UI_Widget* widget, r32 z_start, r32 z_s
if (cmd.baseline_after.x >= desc.p_max.x - 5) if (cmd.baseline_after.x >= desc.p_max.x - 5)
{ {
if (has_flag(child->desc.style.flags, UIWidgetStyle_TextClip)) break;
baseline.x = baseline_x_start; baseline.x = baseline_x_start;
baseline.y += ui->font_ascent + ui->font_descent + ui->font_line_gap; baseline.y += ui->font_ascent + ui->font_descent + ui->font_line_gap;
cmd = ui_sprite_char_get_draw_cmd(ui, baseline, (u32)at); cmd = ui_sprite_char_get_draw_cmd(ui, baseline, (u32)at);
@ -495,12 +458,101 @@ ui_widgets_to_geometry_recursive(UI* ui, UI_Widget* widget, r32 z_start, r32 z_s
} }
/////////////////////////////////////////// ///////////////////////////////////////////
// Specific Widget Implementations // Layout Manager
global UI_Style_Sheet ui_default_style_sheet = { internal void
ui_layout_row_begin(UI_Layout* layout, u32 cols)
{
layout->mode = UILayout_Rows;
layout->cols = cols;
}
internal void
ui_layout_row_begin(UI* ui, u32 cols) { ui_layout_row_begin(ui->layout, cols); }
internal void
ui_layout_row_end(UI_Layout* layout)
{
layout->mode = UILayout_Columns;
}
internal void
ui_layout_row_end(UI* ui) { ui_layout_row_end(ui->layout); }
internal UI_Layout_Bounds
ui_layout_get_next(UI_Layout* layout)
{
UI_Layout_Bounds result = {};
switch (layout->mode)
{ {
case UILayout_Columns:
{
result.min = layout->at;
result.max = v2{ layout->bounds_max.x, layout->at.y + layout->row_height };
layout->at = v2{ layout->bounds_min.x, result.max.y + layout->row_gap};
} break;
case UILayout_Rows:
{
r32 col_width = (layout->bounds_max.x - layout->bounds_min.x) / layout->cols;
col_width -= (layout->cols - 1) * layout->col_gap;
result.min = layout->at;
result.max = v2{ layout->at.x + col_width, layout->at.y + layout->row_height };
layout->at = v2{ result.max.x + layout->col_gap, layout->at.y };
if (layout->at.x >= layout->bounds_max.x)
{
layout->at = v2{
layout->bounds_min.x,
layout->at.y + layout->row_height + layout->row_gap
};
} }
} break;
invalid_default_case;
}
return result;
}
internal UI_Layout_Bounds
ui_layout_get_next(UI* ui) { return ui_layout_get_next(ui->layout); }
///////////////////////////////////////////
// Specific Widget Implementations
//
// These all rely on a layout manager to make calling them simpler
global UI_Style_Sheet ui_default_style_sheet = {};
internal void
ui_create_default_style_sheet()
{
ui_default_style_sheet.styles[UIWidget_Text] = {
(UIWidgetStyle_TextWrap), v4{0,0,0,0}, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_Button] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_Outline | UIWidgetStyle_MouseClick), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_Toggle] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_MouseClick), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_Menu] = ui_default_style_sheet.styles[UIWidget_Toggle];
ui_default_style_sheet.styles[UIWidget_Dropdown] = ui_default_style_sheet.styles[UIWidget_Toggle];
ui_default_style_sheet.styles[UIWidget_HSlider] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_Outline | UIWidgetStyle_MouseDrag), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_VSlider] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_Outline | UIWidgetStyle_MouseDrag), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_HScroll] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_Outline | UIWidgetStyle_MouseDrag), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_VScroll] = {
(UIWidgetStyle_TextClip | UIWidgetStyle_Bg | UIWidgetStyle_Outline | UIWidgetStyle_MouseDrag), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
ui_default_style_sheet.styles[UIWidget_Window] = {
(UIWidgetStyle_TextWrap), BLACK_V4, WHITE_V4, WHITE_SPRITE_ID
};
}; };
internal UI_Widget_Style internal UI_Widget_Style
@ -510,15 +562,46 @@ ui_get_style(UI* ui, UI_Widget_Kind kind)
return ui_default_style_sheet.styles[kind]; return ui_default_style_sheet.styles[kind];
} }
internal UI_Widget_Desc
ui_layout_next_widget(UI* ui, UI_Widget_Kind kind)
{
UI_Layout_Bounds b = ui_layout_get_next(ui);
UI_Widget_Desc d = {};
d.p_min = b.min;
d.p_max = b.max;
d.style = ui_get_style(ui, kind);
return d;
}
internal void internal void
ui_text(UI* ui, String string) ui_text(UI* ui, String string)
{ {
UI_Widget_Desc d = ui_layout_next_widget(ui, UIWidget_Text);
d.string = string;
ui_widget_push(ui, d);
} }
internal bool internal bool
ui_button(UI* ui, String string) ui_button(UI* ui, String string)
{ {
return false; UI_Widget_Desc d = ui_layout_next_widget(ui, UIWidget_Button);
d.string = string;
UI_Widget_Result r = ui_widget_push(ui, d);
return has_flag(r.flags, UIWidgetResult_MouseLeft_WentUp);
} }
internal bool
ui_toggle(UI* ui, String string, bool value)
{
UI_Widget_Desc d = ui_layout_next_widget(ui, UIWidget_Button);
if (value) {
v4 t = d.style.color_fg;
d.style.color_fg = d.style.color_bg;
d.style.color_bg = t;
}
d.string = string;
UI_Widget_Result r = ui_widget_push(ui, d);
bool result = value;
if (has_flag(r.flags, UIWidgetResult_MouseLeft_WentUp)) result = !result;
return result;
}

View File

@ -32,10 +32,11 @@ enum
{ {
UIWidgetStyle_None = 0, UIWidgetStyle_None = 0,
UIWidgetStyle_Bg = 1, UIWidgetStyle_Bg = 1,
UIWidgetStyle_Text = 2, UIWidgetStyle_TextClip = 2,
UIWidgetStyle_Outline = 4, UIWidgetStyle_TextWrap = 4,
UIWidgetStyle_MouseClick = 8, UIWidgetStyle_Outline = 8,
UIWidgetStyle_MouseDrag = 16, UIWidgetStyle_MouseClick = 16,
UIWidgetStyle_MouseDrag = 32,
}; };
// akin to a css class, could be used to style multiple // akin to a css class, could be used to style multiple
@ -119,6 +120,34 @@ struct UI_Widget_Pool
UI_Widget* active_parent; UI_Widget* active_parent;
}; };
enum UI_Layout_Mode
{
// each element takes up a whole row
UILayout_Columns,
// each element takes up one column in the row. If you overflow,
// the layout manager overflows to the next row
UILayout_Rows,
};
struct UI_Layout
{
v2 bounds_min;
v2 bounds_max;
r32 row_height;
r32 row_gap;
r32 col_gap;
v2 at;
UI_Layout_Mode mode;
u32 cols;
};
struct UI_Layout_Bounds
{
v2 min;
v2 max;
};
struct UI struct UI
{ {
UI_Vertex* verts; UI_Vertex* verts;
@ -138,6 +167,8 @@ struct UI
UI_Widget_Id widget_next_hot; UI_Widget_Id widget_next_hot;
UI_Widget_Id widget_hot; UI_Widget_Id widget_hot;
UI_Layout* layout;
// frames since these values were set // frames since these values were set
u16 widget_next_hot_frames; u16 widget_next_hot_frames;
u16 widget_hot_frames; u16 widget_hot_frames;
@ -162,6 +193,8 @@ internal void ui_draw(UI* ui);
// Widgets // Widgets
internal void ui_create_default_style_sheet();
internal UI_Widget_Id ui_widget_id_create(u32 index_in_parent, String string); internal UI_Widget_Id ui_widget_id_create(u32 index_in_parent, String string);
internal bool ui_widget_id_equals(UI_Widget_Id a, UI_Widget_Id b); internal bool ui_widget_id_equals(UI_Widget_Id a, UI_Widget_Id b);
internal bool ui_widget_id_is_valid(UI_Widget_Id h); internal bool ui_widget_id_is_valid(UI_Widget_Id h);

View File

@ -244,6 +244,7 @@ hash_table_find(u32* ids, u32 cap, u32 value)
// Vector Extensions // Vector Extensions
#define v2_to_v3(xy,z) v3{(xy).x, (xy).y, (z)} #define v2_to_v3(xy,z) v3{(xy).x, (xy).y, (z)}
#define v2_floor(v) v2{ floorf(v.x), floorf(v.y) }
#define v3_floor(v) v3{ floorf(v.x), floorf(v.y), floorf(v.z) } #define v3_floor(v) v3{ floorf(v.x), floorf(v.y), floorf(v.z) }
////////////////////////////////////////////// //////////////////////////////////////////////