Compare commits
3 Commits
c0e9565bf6
...
499f6dc79b
Author | SHA1 | Date | |
---|---|---|---|
499f6dc79b | |||
740ea0c6be | |||
8d4b353e88 |
25
src/main.c3
25
src/main.c3
@ -126,7 +126,6 @@ fn int main(String[] args)
|
|||||||
if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) {
|
if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) {
|
||||||
io::printn("press button0");
|
io::printn("press button0");
|
||||||
toggle = !toggle;
|
toggle = !toggle;
|
||||||
ui.force_update()!!;
|
|
||||||
}
|
}
|
||||||
//ui.layout_next_column()!!;
|
//ui.layout_next_column()!!;
|
||||||
if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) {
|
if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) {
|
||||||
@ -138,21 +137,21 @@ fn int main(String[] args)
|
|||||||
io::printn("release button2");
|
io::printn("release button2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) {
|
static float slider1;
|
||||||
ugui::Elem* e = ui.get_elem_by_label("slider")!!;
|
if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider1)!!.update) {
|
||||||
io::printfn("slider: %f", e.slider.value);
|
io::printfn("slider: %f", slider1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!;
|
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!;
|
||||||
|};
|
|};
|
||||||
ui.div_end()!!;
|
ui.div_end()!!;
|
||||||
|
|
||||||
ui.div_begin("second", ugui::DIV_FILL, scroll_y: true)!!;
|
ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
|
||||||
{|
|
{|
|
||||||
ui.layout_set_column()!!;
|
ui.layout_set_column()!!;
|
||||||
if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) {
|
static float slider2 = 0.5;
|
||||||
ugui::Elem* e = ui.get_elem_by_label("slider_other")!!;
|
if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100}, &slider2)!!.update) {
|
||||||
io::printfn("other slider: %f", e.slider.value);
|
io::printfn("other slider: %f", slider2);
|
||||||
}
|
}
|
||||||
ui.button("button10", ugui::Rect{0,0,50,50})!!;
|
ui.button("button10", ugui::Rect{0,0,50,50})!!;
|
||||||
ui.button("button11", ugui::Rect{0,0,50,50})!!;
|
ui.button("button11", ugui::Rect{0,0,50,50})!!;
|
||||||
@ -164,6 +163,11 @@ fn int main(String[] args)
|
|||||||
ui.button("button16", ugui::Rect{0,0,50,50})!!;
|
ui.button("button16", ugui::Rect{0,0,50,50})!!;
|
||||||
ui.button("button17", ugui::Rect{0,0,50,50})!!;
|
ui.button("button17", ugui::Rect{0,0,50,50})!!;
|
||||||
}
|
}
|
||||||
|
ui.layout_next_column()!!;
|
||||||
|
ui.layout_set_row()!!;
|
||||||
|
static float f1, f2;
|
||||||
|
ui.slider_hor("hs1", ugui::Rect{0,0,100,30}, &f1)!!;
|
||||||
|
ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!;
|
||||||
|};
|
|};
|
||||||
ui.div_end()!!;
|
ui.div_end()!!;
|
||||||
|
|
||||||
@ -176,11 +180,6 @@ fn int main(String[] args)
|
|||||||
{|
|
{|
|
||||||
ui.layout_set_row()!!;
|
ui.layout_set_row()!!;
|
||||||
ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
||||||
|
|
||||||
//ui.layout_next_row()!!;
|
|
||||||
//ui.text_unbounded("draw avg", string::tformat("draw avg: %s", dts.avg))!!;
|
|
||||||
|
|
||||||
//ui.force_update()!!;
|
|
||||||
|};
|
|};
|
||||||
ui.div_end()!!;
|
ui.div_end()!!;
|
||||||
|
|
||||||
|
@ -33,10 +33,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false)
|
|||||||
return UgError.WRONG_ELEMENT_TYPE?;
|
return UgError.WRONG_ELEMENT_TYPE?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the element is new or the parent was updated then redo layout
|
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||||
if (needs_layout) {
|
|
||||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the bounds are null the element is outside the div view,
|
// if the bounds are null the element is outside the div view,
|
||||||
// no interaction should occur so just return
|
// no interaction should occur so just return
|
||||||
|
321
src/ugui_core.c3
Normal file
321
src/ugui_core.c3
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
module ugui;
|
||||||
|
|
||||||
|
import vtree;
|
||||||
|
import cache;
|
||||||
|
import fifo;
|
||||||
|
|
||||||
|
import std::io;
|
||||||
|
import std::core::string;
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
short x, y, w, h;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
short x, y;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Color{
|
||||||
|
char r, g, b, a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// element ids are just long ints
|
||||||
|
def Id = usz;
|
||||||
|
|
||||||
|
enum ElemType {
|
||||||
|
ETYPE_NONE,
|
||||||
|
ETYPE_DIV,
|
||||||
|
ETYPE_BUTTON,
|
||||||
|
ETYPE_SLIDER,
|
||||||
|
ETYPE_TEXT,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitstruct ElemFlags : uint {
|
||||||
|
bool updated : 0;
|
||||||
|
bool has_focus : 1;
|
||||||
|
bool is_new : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitstruct ElemEvents : uint {
|
||||||
|
bool key_press : 0;
|
||||||
|
bool key_release : 1;
|
||||||
|
bool key_hold : 2;
|
||||||
|
bool mouse_hover : 3;
|
||||||
|
bool mouse_press : 4;
|
||||||
|
bool mouse_release : 5;
|
||||||
|
bool mouse_hold : 6;
|
||||||
|
bool update : 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElemText {
|
||||||
|
char* str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// element structure
|
||||||
|
struct Elem {
|
||||||
|
Id id;
|
||||||
|
ElemFlags flags;
|
||||||
|
ElemEvents events;
|
||||||
|
Rect bounds;
|
||||||
|
ElemType type;
|
||||||
|
union {
|
||||||
|
ElemDiv div;
|
||||||
|
ElemButton button;
|
||||||
|
ElemSlider slider;
|
||||||
|
ElemText text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// relationships between elements are stored in a tree, it stores just the ids
|
||||||
|
def IdTree = vtree::VTree(<Id>) @private;
|
||||||
|
|
||||||
|
// elements themselves are kept in a cache
|
||||||
|
const uint MAX_ELEMENTS = 1024;
|
||||||
|
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
|
||||||
|
|
||||||
|
def CmdQueue = fifo::Fifo(<Cmd>);
|
||||||
|
|
||||||
|
fault UgError {
|
||||||
|
INVALID_SIZE,
|
||||||
|
EVENT_UNSUPPORTED,
|
||||||
|
UNEXPECTED_ELEMENT,
|
||||||
|
WRONG_ELEMENT_TYPE,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Color uint_to_rgba(uint $u) {
|
||||||
|
return Color{
|
||||||
|
.r = (char)(($u >> 24) & 0xff),
|
||||||
|
.g = (char)(($u >> 16) & 0xff),
|
||||||
|
.b = (char)(($u >> 8) & 0xff),
|
||||||
|
.a = (char)(($u >> 0) & 0xff)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||||
|
|
||||||
|
const uint STACK_STEP = 10;
|
||||||
|
const uint MAX_ELEMS = 128;
|
||||||
|
const uint MAX_CMDS = 256;
|
||||||
|
const uint ROOT_ID = 1;
|
||||||
|
|
||||||
|
enum Layout {
|
||||||
|
ROW,
|
||||||
|
COLUMN,
|
||||||
|
FLOATING
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
Layout layout;
|
||||||
|
IdTree tree;
|
||||||
|
ElemCache cache;
|
||||||
|
CmdQueue cmd_queue;
|
||||||
|
// total size in pixels of the context
|
||||||
|
ushort width, height;
|
||||||
|
Style style;
|
||||||
|
Font font;
|
||||||
|
|
||||||
|
bool has_focus;
|
||||||
|
struct input {
|
||||||
|
InputEvents events;
|
||||||
|
struct mouse {
|
||||||
|
Point pos, delta;
|
||||||
|
// mouse_down: bitmap of mouse buttons that are held
|
||||||
|
// mouse_updated: bitmap of mouse buttons that have been updated
|
||||||
|
// mouse_released = mouse_updated & ~mouse_down
|
||||||
|
// mouse_pressed = mouse_updated & mouse_down
|
||||||
|
MouseButtons down;
|
||||||
|
MouseButtons updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isz active_div; // tree node indicating the current active div
|
||||||
|
}
|
||||||
|
|
||||||
|
macro point_in_rect(Point p, Rect r)
|
||||||
|
{
|
||||||
|
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if rect a contains b
|
||||||
|
macro bool Rect.contains(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Rect Rect.intersection(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = (short)max(a.x, b.x),
|
||||||
|
.y = (short)max(a.y, b.y),
|
||||||
|
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
||||||
|
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// rect intersection not null
|
||||||
|
macro bool Rect.collides(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
||||||
|
|
||||||
|
// return a pointer to the parent of the current active div
|
||||||
|
fn Elem*! Ctx.get_parent(&ctx)
|
||||||
|
{
|
||||||
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
return ctx.cache.search(parent_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get or push an element from the cache, return a pointer to it
|
||||||
|
// resets all flags except is_new which is set accordingly
|
||||||
|
macro Ctx.get_elem(&ctx, Id id)
|
||||||
|
{
|
||||||
|
Elem empty_elem;
|
||||||
|
bool is_new;
|
||||||
|
Elem* c_elem;
|
||||||
|
c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!;
|
||||||
|
c_elem.flags = (ElemFlags)0;
|
||||||
|
c_elem.flags.is_new = is_new;
|
||||||
|
return c_elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this searches an element in the cache by label, it does not create a new element
|
||||||
|
// if it does't find one
|
||||||
|
macro Ctx.get_elem_by_label(&ctx, String label)
|
||||||
|
{
|
||||||
|
Id id = label.hash();
|
||||||
|
return ctx.cache.search(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private
|
||||||
|
{
|
||||||
|
Id id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
return ctx.cache.search(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void! Ctx.init(&ctx)
|
||||||
|
{
|
||||||
|
ctx.tree.init(MAX_ELEMENTS)!;
|
||||||
|
defer catch { (void)ctx.tree.free(); }
|
||||||
|
|
||||||
|
ctx.cache.init()!;
|
||||||
|
defer catch { (void)ctx.cache.free(); }
|
||||||
|
|
||||||
|
ctx.cmd_queue.init(MAX_ELEMENTS)!;
|
||||||
|
defer catch { (void)ctx.cmd_queue.free(); }
|
||||||
|
|
||||||
|
ctx.layout = Layout.ROW;
|
||||||
|
ctx.active_div = 0;
|
||||||
|
|
||||||
|
// TODO: add style config
|
||||||
|
ctx.style.margin = Rect{2, 2, 2, 2};
|
||||||
|
ctx.style.border = Rect{2, 2, 2, 2};
|
||||||
|
ctx.style.padding = Rect{1, 1, 1, 1};
|
||||||
|
ctx.style.radius = 5;
|
||||||
|
ctx.style.bgcolor = uint_to_rgba(0x282828ff);
|
||||||
|
ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff);
|
||||||
|
ctx.style.brcolor = uint_to_rgba(0xd79921ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void Ctx.free(&ctx)
|
||||||
|
{
|
||||||
|
(void)ctx.tree.free();
|
||||||
|
(void)ctx.cache.free();
|
||||||
|
(void)ctx.cmd_queue.free();
|
||||||
|
(void)ctx.font.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void! Ctx.frame_begin(&ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
// 2. Get the root element from the cache and update it
|
||||||
|
Elem* c_elem = ctx.get_elem(ROOT_ID)!;
|
||||||
|
// The root should have the updated flag only if the size of the window
|
||||||
|
// was changed between frames, this propagates an element size recalculation
|
||||||
|
// down the element tree
|
||||||
|
c_elem.flags.updated = ctx.input.events.resize;
|
||||||
|
// if the window has focus then the root element also has focus, no other
|
||||||
|
// computation needed, child elements need to check the mouse positon and
|
||||||
|
// other stuff
|
||||||
|
c_elem.flags.has_focus = ctx.has_focus;
|
||||||
|
|
||||||
|
Elem def_root = {
|
||||||
|
.id = ROOT_ID,
|
||||||
|
.type = ETYPE_DIV,
|
||||||
|
.bounds = {
|
||||||
|
.w = ctx.width,
|
||||||
|
.h = ctx.height,
|
||||||
|
},
|
||||||
|
.div = {
|
||||||
|
.layout = LAYOUT_ROW,
|
||||||
|
.children_bounds = {
|
||||||
|
.w = ctx.width,
|
||||||
|
.h = ctx.height,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.flags = c_elem.flags,
|
||||||
|
};
|
||||||
|
|
||||||
|
*c_elem = def_root;
|
||||||
|
|
||||||
|
// 3. Push the root element into the element tree
|
||||||
|
ctx.active_div = ctx.tree.add(ROOT_ID, 0)!;
|
||||||
|
|
||||||
|
// The root element does not push anything to the stack
|
||||||
|
// TODO: add a background color taken from a theme or config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void! Ctx.frame_end(&ctx)
|
||||||
|
{
|
||||||
|
Elem* root = ctx.get_elem_by_tree_idx(0)!;
|
||||||
|
root.div.layout = LAYOUT_ROW;
|
||||||
|
|
||||||
|
// 1. clear the tree
|
||||||
|
ctx.tree.nuke();
|
||||||
|
|
||||||
|
// 2. clear input fields
|
||||||
|
ctx.input.events = (InputEvents)0;
|
||||||
|
|
||||||
|
// send atlas updates
|
||||||
|
if (ctx.font.should_update) {
|
||||||
|
ctx.push_update_atlas(&ctx.font.atlas)!;
|
||||||
|
ctx.font.should_update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$if 1:
|
||||||
|
// draw mouse position
|
||||||
|
Cmd cmd = {
|
||||||
|
.type = CMD_RECT,
|
||||||
|
.rect.rect = {
|
||||||
|
.x = ctx.input.mouse.pos.x - 2,
|
||||||
|
.y = ctx.input.mouse.pos.y - 2,
|
||||||
|
.w = 4,
|
||||||
|
.h = 4,
|
||||||
|
},
|
||||||
|
.rect.color = uint_to_rgba(0xff00ffff)
|
||||||
|
};
|
||||||
|
ctx.cmd_queue.enqueue(&cmd)!;
|
||||||
|
$endif
|
||||||
|
}
|
||||||
|
|
||||||
|
<*
|
||||||
|
* @ensure elem != null
|
||||||
|
*>
|
||||||
|
fn bool Ctx.is_hovered(&ctx, Elem *elem)
|
||||||
|
{
|
||||||
|
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
|
||||||
|
}
|
183
src/ugui_data.c3
183
src/ugui_data.c3
@ -1,183 +0,0 @@
|
|||||||
module ugui;
|
|
||||||
|
|
||||||
import std::core::string;
|
|
||||||
|
|
||||||
import vtree;
|
|
||||||
import cache;
|
|
||||||
import fifo;
|
|
||||||
|
|
||||||
|
|
||||||
struct Rect {
|
|
||||||
short x, y, w, h;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
short x, y;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Color{
|
|
||||||
char r, g, b, a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// element ids are just long ints
|
|
||||||
def Id = usz;
|
|
||||||
|
|
||||||
enum ElemType {
|
|
||||||
ETYPE_NONE,
|
|
||||||
ETYPE_DIV,
|
|
||||||
ETYPE_BUTTON,
|
|
||||||
ETYPE_SLIDER,
|
|
||||||
ETYPE_TEXT,
|
|
||||||
}
|
|
||||||
|
|
||||||
bitstruct ElemFlags : uint {
|
|
||||||
bool updated : 0;
|
|
||||||
bool has_focus : 1;
|
|
||||||
bool is_new : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitstruct ElemEvents : uint {
|
|
||||||
bool key_press : 0;
|
|
||||||
bool key_release : 1;
|
|
||||||
bool key_hold : 2;
|
|
||||||
bool mouse_hover : 3;
|
|
||||||
bool mouse_press : 4;
|
|
||||||
bool mouse_release : 5;
|
|
||||||
bool mouse_hold : 6;
|
|
||||||
bool update : 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// slider element
|
|
||||||
struct ElemSlider {
|
|
||||||
float value;
|
|
||||||
Rect handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ElemText {
|
|
||||||
char* str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// element structure
|
|
||||||
struct Elem {
|
|
||||||
Id id;
|
|
||||||
ElemFlags flags;
|
|
||||||
ElemEvents events;
|
|
||||||
Rect bounds;
|
|
||||||
ElemType type;
|
|
||||||
union {
|
|
||||||
ElemDiv div;
|
|
||||||
ElemButton button;
|
|
||||||
ElemSlider slider;
|
|
||||||
ElemText text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// relationships between elements are stored in a tree, it stores just the ids
|
|
||||||
def IdTree = vtree::VTree(<Id>) @private;
|
|
||||||
|
|
||||||
// elements themselves are kept in a cache
|
|
||||||
const uint MAX_ELEMENTS = 1024;
|
|
||||||
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
|
|
||||||
|
|
||||||
def CmdQueue = fifo::Fifo(<Cmd>);
|
|
||||||
|
|
||||||
fault UgError {
|
|
||||||
INVALID_SIZE,
|
|
||||||
EVENT_UNSUPPORTED,
|
|
||||||
UNEXPECTED_ELEMENT,
|
|
||||||
WRONG_ELEMENT_TYPE,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro Color uint_to_rgba(uint $u) {
|
|
||||||
return Color{
|
|
||||||
.r = (char)(($u >> 24) & 0xff),
|
|
||||||
.g = (char)(($u >> 16) & 0xff),
|
|
||||||
.b = (char)(($u >> 8) & 0xff),
|
|
||||||
.a = (char)(($u >> 0) & 0xff)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
|
|
||||||
|
|
||||||
macro abs(a) { return a < 0 ? -a : a; }
|
|
||||||
macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); }
|
|
||||||
|
|
||||||
const uint STACK_STEP = 10;
|
|
||||||
const uint MAX_ELEMS = 128;
|
|
||||||
const uint MAX_CMDS = 256;
|
|
||||||
const uint ROOT_ID = 1;
|
|
||||||
|
|
||||||
enum Layout {
|
|
||||||
ROW,
|
|
||||||
COLUMN,
|
|
||||||
FLOATING
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
Layout layout;
|
|
||||||
IdTree tree;
|
|
||||||
ElemCache cache;
|
|
||||||
CmdQueue cmd_queue;
|
|
||||||
// total size in pixels of the context
|
|
||||||
ushort width, height;
|
|
||||||
Style style;
|
|
||||||
Font font;
|
|
||||||
|
|
||||||
bool has_focus;
|
|
||||||
struct input {
|
|
||||||
InputEvents events;
|
|
||||||
struct mouse {
|
|
||||||
Point pos, delta;
|
|
||||||
// mouse_down: bitmap of mouse buttons that are held
|
|
||||||
// mouse_updated: bitmap of mouse buttons that have been updated
|
|
||||||
// mouse_released = mouse_updated & ~mouse_down
|
|
||||||
// mouse_pressed = mouse_updated & mouse_down
|
|
||||||
MouseButtons down;
|
|
||||||
MouseButtons updated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isz active_div; // tree node indicating the current active div
|
|
||||||
}
|
|
||||||
|
|
||||||
macro point_in_rect(Point p, Rect r)
|
|
||||||
{
|
|
||||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true if rect a contains b
|
|
||||||
macro bool Rect.contains(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro Rect Rect.intersection(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return Rect{
|
|
||||||
.x = (short)max(a.x, b.x),
|
|
||||||
.y = (short)max(a.y, b.y),
|
|
||||||
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
|
||||||
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// rect intersection not null
|
|
||||||
macro bool Rect.collides(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
|
160
src/ugui_div.c3
160
src/ugui_div.c3
@ -1,6 +1,7 @@
|
|||||||
module ugui;
|
module ugui;
|
||||||
|
|
||||||
import std::io;
|
import std::io;
|
||||||
|
import std::math;
|
||||||
|
|
||||||
enum DivLayout {
|
enum DivLayout {
|
||||||
LAYOUT_ROW,
|
LAYOUT_ROW,
|
||||||
@ -19,7 +20,8 @@ struct ElemDiv {
|
|||||||
float value_x;
|
float value_x;
|
||||||
float value_y;
|
float value_y;
|
||||||
}
|
}
|
||||||
Rect children_bounds;
|
Rect children_bounds; // current frame children bounds
|
||||||
|
Rect pcb; // previous frame children bounds
|
||||||
Point origin_r, origin_c;
|
Point origin_r, origin_c;
|
||||||
Color bgcolor;
|
Color bgcolor;
|
||||||
}
|
}
|
||||||
@ -38,12 +40,12 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
|||||||
{
|
{
|
||||||
Id id = label.hash();
|
Id id = label.hash();
|
||||||
|
|
||||||
Elem *parent = ctx.get_parent()!;
|
Elem* parent = ctx.get_parent()!;
|
||||||
Elem* c_elem = ctx.get_elem(id)!;
|
Elem* c_elem = ctx.get_elem(id)!;
|
||||||
isz div_node = ctx.tree.add(id, ctx.active_div)!;
|
isz div_node = ctx.tree.add(id, ctx.active_div)!;
|
||||||
ctx.active_div = div_node;
|
ctx.active_div = div_node;
|
||||||
|
|
||||||
bool needs_layout = c_elem.flags.is_new || parent.flags.updated;
|
bool is_new = c_elem.flags.is_new;
|
||||||
|
|
||||||
if (c_elem.flags.is_new) {
|
if (c_elem.flags.is_new) {
|
||||||
*c_elem = DIV_DEFAULTS;
|
*c_elem = DIV_DEFAULTS;
|
||||||
@ -53,86 +55,104 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
|||||||
c_elem.div.scroll.can_x = scroll_x;
|
c_elem.div.scroll.can_x = scroll_x;
|
||||||
c_elem.div.scroll.can_y = scroll_y;
|
c_elem.div.scroll.can_y = scroll_y;
|
||||||
|
|
||||||
// do layout and update flags only if the element was updated
|
// 2. layout the element
|
||||||
if (needs_layout) {
|
c_elem.bounds = ctx.position_element(parent, size);
|
||||||
// 2. layout the element
|
c_elem.div.children_bounds = c_elem.bounds;
|
||||||
c_elem.bounds = ctx.position_element(parent, size);
|
c_elem.div.bgcolor = ctx.style.bgcolor;
|
||||||
c_elem.div.children_bounds = c_elem.bounds;
|
|
||||||
c_elem.div.bgcolor = ctx.style.bgcolor;
|
|
||||||
|
|
||||||
// 3. Mark the element as updated
|
// 4. Fill the div fields
|
||||||
c_elem.flags.updated = true;
|
c_elem.div.origin_c = Point{
|
||||||
// 4. Fill the div fields
|
.x = c_elem.bounds.x,
|
||||||
c_elem.div.origin_c = Point{
|
.y = c_elem.bounds.y,
|
||||||
.x = c_elem.bounds.x,
|
};
|
||||||
.y = c_elem.bounds.y,
|
c_elem.div.origin_r = c_elem.div.origin_c;
|
||||||
};
|
c_elem.div.layout = parent.div.layout;
|
||||||
c_elem.div.origin_r = c_elem.div.origin_c;
|
|
||||||
c_elem.div.layout = parent.div.layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the background to the draw stack
|
// Add the background to the draw stack
|
||||||
bool do_border = parent.div.layout == LAYOUT_FLOATING;
|
bool do_border = parent.div.layout == LAYOUT_FLOATING;
|
||||||
ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!;
|
ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!;
|
||||||
|
|
||||||
|
c_elem.events = ctx.get_elem_events(c_elem);
|
||||||
|
|
||||||
// TODO: check active
|
// TODO: check active
|
||||||
// TODO: check resizeable
|
// TODO: check resizeable
|
||||||
|
|
||||||
// check and draw scroll bars
|
// FIXME: we cannot use slider elements as scrollbars because of id management
|
||||||
if (!c_elem.bounds.contains(c_elem.div.children_bounds)) {
|
// scrollbars
|
||||||
Point cbc = {
|
Rect cb = c_elem.div.pcb;
|
||||||
.x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w,
|
// children bounds bottom-right corner
|
||||||
.y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h,
|
Point cbc = {
|
||||||
|
.x = cb.x + cb.w,
|
||||||
|
.y = cb.y + cb.h,
|
||||||
|
};
|
||||||
|
// div bounds bottom-right corner
|
||||||
|
Point bc = {
|
||||||
|
.x = c_elem.bounds.x + c_elem.bounds.w,
|
||||||
|
.y = c_elem.bounds.y + c_elem.bounds.h,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// set the scrollbar flag, is used in layout
|
||||||
|
// vertical overflow
|
||||||
|
c_elem.div.scroll.on_y = cbc.y > bc.y && c_elem.div.scroll.can_y;
|
||||||
|
// horizontal overflow
|
||||||
|
c_elem.div.scroll.on_x = cbc.x > bc.x && c_elem.div.scroll.can_x;
|
||||||
|
|
||||||
|
if (c_elem.div.scroll.on_y) {
|
||||||
|
Rect vslider = {
|
||||||
|
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
||||||
|
.y = c_elem.bounds.y,
|
||||||
|
.w = 10,
|
||||||
|
.h = c_elem.bounds.h,
|
||||||
};
|
};
|
||||||
Point bc = {
|
|
||||||
.x = c_elem.bounds.x + c_elem.bounds.w,
|
short hh = (short)(max((float)bc.y / cbc.y, (float)0.15) * c_elem.bounds.h);
|
||||||
.y = c_elem.bounds.y + c_elem.bounds.h,
|
Rect vhandle = {
|
||||||
|
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
||||||
|
.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y),
|
||||||
|
.w = 10,
|
||||||
|
.h = hh,
|
||||||
};
|
};
|
||||||
// vertical overflow, check and draw scroll bar
|
|
||||||
if (cbc.y > bc.y && c_elem.div.scroll.can_y) {
|
|
||||||
// set the scrollbar flag, is used in layout
|
|
||||||
c_elem.div.scroll.on_y = true;
|
|
||||||
|
|
||||||
Rect vslider = {
|
Point m = ctx.input.mouse.pos;
|
||||||
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
if (parent.flags.has_focus && c_elem.events.mouse_hover &&
|
||||||
.y = c_elem.bounds.y,
|
c_elem.events.mouse_hold && point_in_rect(m, vhandle)) {
|
||||||
.w = 10,
|
vhandle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y);
|
||||||
.h = c_elem.bounds.h,
|
c_elem.div.scroll.value_y = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh);
|
||||||
};
|
c_elem.flags.updated = true;
|
||||||
|
|
||||||
float vh = max((float)bc.y / cbc.y, (float)0.15);
|
|
||||||
Rect vhandle = {
|
|
||||||
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
|
||||||
.y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1-vh) * c_elem.div.scroll.value_y)),
|
|
||||||
.w = 10,
|
|
||||||
.h = (short)(c_elem.bounds.h * vh),
|
|
||||||
};
|
|
||||||
|
|
||||||
c_elem.events = ctx.get_elem_events(c_elem);
|
|
||||||
if (parent.flags.has_focus && c_elem.events.mouse_hover && c_elem.events.mouse_hold && point_in_rect(ctx.input.mouse.pos, vhandle)) {
|
|
||||||
short y = (short)clamp(ctx.input.mouse.pos.y - vhandle.h/2, vslider.y, vslider.y + vslider.h - vhandle.h);
|
|
||||||
vhandle.y = y;
|
|
||||||
float v = (float)(vhandle.y-vslider.y) / (float)(vslider.h-vhandle.h);
|
|
||||||
c_elem.div.scroll.value_y = v;
|
|
||||||
c_elem.flags.updated = true;
|
|
||||||
c_elem.div.origin_c = Point{
|
|
||||||
.x = c_elem.bounds.x,
|
|
||||||
.y = c_elem.bounds.y,
|
|
||||||
};
|
|
||||||
c_elem.div.origin_r = c_elem.div.origin_c;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!;
|
|
||||||
ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!;
|
|
||||||
} else {
|
|
||||||
c_elem.div.scroll.on_y = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!;
|
||||||
|
ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the bounds are outside of the view then allocate space for scrollbars
|
if (c_elem.div.scroll.on_x) {
|
||||||
DivLayout old_layout = c_elem.div.layout;
|
Rect hslider = {
|
||||||
c_elem.div.layout = LAYOUT_FLOATING;
|
.x = c_elem.bounds.x,
|
||||||
c_elem.div.layout = old_layout;
|
.y = c_elem.bounds.y + c_elem.bounds.h - 10,
|
||||||
|
.w = c_elem.bounds.w,
|
||||||
|
.h = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
short hw = (short)(max((float)bc.x / cbc.x, (float)0.15) * c_elem.bounds.w);
|
||||||
|
Rect hhandle = {
|
||||||
|
.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x),
|
||||||
|
.y = c_elem.bounds.y + c_elem.bounds.h - 10,
|
||||||
|
.w = hw,
|
||||||
|
.h = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
Point m = ctx.input.mouse.pos;
|
||||||
|
if (parent.flags.has_focus && c_elem.events.mouse_hover &&
|
||||||
|
c_elem.events.mouse_hold && point_in_rect(m, hhandle)) {
|
||||||
|
hhandle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x);
|
||||||
|
c_elem.div.scroll.value_x = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw);
|
||||||
|
c_elem.flags.updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.push_rect(hslider, uint_to_rgba(0x999999ff))!;
|
||||||
|
ctx.push_rect(hhandle, uint_to_rgba(0x9999ffff))!;
|
||||||
|
}
|
||||||
|
|
||||||
if (parent.flags.has_focus) {
|
if (parent.flags.has_focus) {
|
||||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) {
|
if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) {
|
||||||
@ -144,6 +164,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
|||||||
|
|
||||||
fn void! Ctx.div_end(&ctx)
|
fn void! Ctx.div_end(&ctx)
|
||||||
{
|
{
|
||||||
|
// swap the children bounds
|
||||||
|
Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!;
|
||||||
|
c_elem.div.pcb = c_elem.div.children_bounds;
|
||||||
|
|
||||||
// the active_div returns to the parent of the current one
|
// the active_div returns to the parent of the current one
|
||||||
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
||||||
}
|
}
|
||||||
|
162
src/ugui_impl.c3
162
src/ugui_impl.c3
@ -1,162 +0,0 @@
|
|||||||
module ugui;
|
|
||||||
|
|
||||||
import std::io;
|
|
||||||
|
|
||||||
// return a pointer to the parent of the current active div
|
|
||||||
fn Elem*! Ctx.get_parent(&ctx)
|
|
||||||
{
|
|
||||||
// FIXME: if the tree held pointers to the elements then no more
|
|
||||||
// redundant cache search
|
|
||||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
|
||||||
return ctx.cache.search(parent_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get or push an element from the cache, return a pointer to it
|
|
||||||
// resets all flags except is_new which is set accordingly
|
|
||||||
macro Ctx.get_elem(&ctx, Id id)
|
|
||||||
{
|
|
||||||
Elem empty_elem;
|
|
||||||
bool is_new;
|
|
||||||
Elem* c_elem;
|
|
||||||
c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!;
|
|
||||||
c_elem.flags = (ElemFlags)0;
|
|
||||||
c_elem.flags.is_new = is_new;
|
|
||||||
return c_elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this searches an element in the cache by label, it does not create a new element
|
|
||||||
// if it does't find one
|
|
||||||
macro Ctx.get_elem_by_label(&ctx, String label)
|
|
||||||
{
|
|
||||||
Id id = label.hash();
|
|
||||||
return ctx.cache.search(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private
|
|
||||||
{
|
|
||||||
Id id = ctx.tree.get(ctx.active_div)!;
|
|
||||||
return ctx.cache.search(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn void! Ctx.init(&ctx)
|
|
||||||
{
|
|
||||||
ctx.tree.init(MAX_ELEMENTS)!;
|
|
||||||
defer catch { (void)ctx.tree.free(); }
|
|
||||||
|
|
||||||
ctx.cache.init()!;
|
|
||||||
defer catch { (void)ctx.cache.free(); }
|
|
||||||
|
|
||||||
ctx.cmd_queue.init(MAX_ELEMENTS)!;
|
|
||||||
defer catch { (void)ctx.cmd_queue.free(); }
|
|
||||||
|
|
||||||
ctx.layout = Layout.ROW;
|
|
||||||
ctx.active_div = 0;
|
|
||||||
|
|
||||||
// TODO: add style config
|
|
||||||
ctx.style.margin = Rect{2, 2, 2, 2};
|
|
||||||
ctx.style.border = Rect{2, 2, 2, 2};
|
|
||||||
ctx.style.padding = Rect{1, 1, 1, 1};
|
|
||||||
ctx.style.radius = 5;
|
|
||||||
ctx.style.bgcolor = uint_to_rgba(0x282828ff);
|
|
||||||
ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff);
|
|
||||||
ctx.style.brcolor = uint_to_rgba(0xd79921ff);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn void Ctx.free(&ctx)
|
|
||||||
{
|
|
||||||
(void)ctx.tree.free();
|
|
||||||
(void)ctx.cache.free();
|
|
||||||
(void)ctx.cmd_queue.free();
|
|
||||||
(void)ctx.font.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn void! Ctx.frame_begin(&ctx)
|
|
||||||
{
|
|
||||||
|
|
||||||
// 2. Get the root element from the cache and update it
|
|
||||||
Elem* c_elem = ctx.get_elem(ROOT_ID)!;
|
|
||||||
// The root should have the updated flag only if the size of the window
|
|
||||||
// was changed between frames, this propagates an element size recalculation
|
|
||||||
// down the element tree
|
|
||||||
c_elem.flags.updated = ctx.input.events.resize | ctx.input.events.force_update;
|
|
||||||
ctx.input.events.force_update = false;
|
|
||||||
// if the window has focus then the root element also has focus, no other
|
|
||||||
// computation needed, child elements need to check the mouse positon and
|
|
||||||
// other stuff
|
|
||||||
c_elem.flags.has_focus = ctx.has_focus;
|
|
||||||
|
|
||||||
if (c_elem.flags.is_new || c_elem.flags.updated) {
|
|
||||||
Elem def_root = {
|
|
||||||
.id = ROOT_ID,
|
|
||||||
.type = ETYPE_DIV,
|
|
||||||
.bounds = {
|
|
||||||
.w = ctx.width,
|
|
||||||
.h = ctx.height,
|
|
||||||
},
|
|
||||||
.div = {
|
|
||||||
.layout = LAYOUT_ROW,
|
|
||||||
.children_bounds = {
|
|
||||||
.w = ctx.width,
|
|
||||||
.h = ctx.height,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.flags = c_elem.flags,
|
|
||||||
};
|
|
||||||
|
|
||||||
*c_elem = def_root;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Push the root element into the element tree
|
|
||||||
ctx.active_div = ctx.tree.add(ROOT_ID, 0)!;
|
|
||||||
|
|
||||||
// The root element does not push anything to the stack
|
|
||||||
// TODO: add a background color taken from a theme or config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn void! Ctx.force_update(&ctx)
|
|
||||||
{
|
|
||||||
ctx.input.events.force_update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn void! Ctx.frame_end(&ctx)
|
|
||||||
{
|
|
||||||
Elem* root = ctx.get_elem_by_tree_idx(0)!;
|
|
||||||
root.div.layout = LAYOUT_ROW;
|
|
||||||
|
|
||||||
// 1. clear the tree
|
|
||||||
ctx.tree.nuke();
|
|
||||||
|
|
||||||
// 2. clear input fields
|
|
||||||
bool f = ctx.input.events.force_update;
|
|
||||||
ctx.input.events = (InputEvents)0;
|
|
||||||
ctx.input.events.force_update = f;
|
|
||||||
|
|
||||||
// send atlas updates
|
|
||||||
if (ctx.font.should_update) {
|
|
||||||
ctx.push_update_atlas(&ctx.font.atlas)!;
|
|
||||||
ctx.font.should_update = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$if 1:
|
|
||||||
// draw mouse position
|
|
||||||
Cmd cmd = {
|
|
||||||
.type = CMD_RECT,
|
|
||||||
.rect.rect = {
|
|
||||||
.x = ctx.input.mouse.pos.x - 2,
|
|
||||||
.y = ctx.input.mouse.pos.y - 2,
|
|
||||||
.w = 4,
|
|
||||||
.h = 4,
|
|
||||||
},
|
|
||||||
.rect.color = uint_to_rgba(0xff00ffff)
|
|
||||||
};
|
|
||||||
ctx.cmd_queue.enqueue(&cmd)!;
|
|
||||||
$endif
|
|
||||||
}
|
|
||||||
|
|
||||||
<*
|
|
||||||
* @ensure elem != null
|
|
||||||
*>
|
|
||||||
fn bool Ctx.is_hovered(&ctx, Elem *elem)
|
|
||||||
{
|
|
||||||
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
module ugui;
|
module ugui;
|
||||||
|
|
||||||
import std::io;
|
import std::io;
|
||||||
|
import std::math;
|
||||||
|
|
||||||
// TODO: this could be a bitstruct
|
// TODO: this could be a bitstruct
|
||||||
bitstruct InputEvents : uint {
|
bitstruct InputEvents : uint {
|
||||||
@ -8,7 +9,6 @@ bitstruct InputEvents : uint {
|
|||||||
bool change_focus : 1; // window focus changed
|
bool change_focus : 1; // window focus changed
|
||||||
bool mouse_move : 2; // mouse was moved
|
bool mouse_move : 2; // mouse was moved
|
||||||
bool mouse_btn : 3; // mouse button pressed or released
|
bool mouse_btn : 3; // mouse button pressed or released
|
||||||
bool force_update : 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window size was changed
|
// Window size was changed
|
||||||
@ -84,8 +84,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
|||||||
// Mouse was moved, report absolute position
|
// Mouse was moved, report absolute position
|
||||||
fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
||||||
{
|
{
|
||||||
ctx.input.mouse.pos.x = clamp(x, 0u16, ctx.width);
|
ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width);
|
||||||
ctx.input.mouse.pos.y = clamp(y, 0u16, ctx.height);
|
ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height);
|
||||||
|
|
||||||
short dx, dy;
|
short dx, dy;
|
||||||
dx = x - ctx.input.mouse.pos.x;
|
dx = x - ctx.input.mouse.pos.x;
|
||||||
@ -107,8 +107,8 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
|
|||||||
mx = ctx.input.mouse.pos.x + dx;
|
mx = ctx.input.mouse.pos.x + dx;
|
||||||
my = ctx.input.mouse.pos.y + dy;
|
my = ctx.input.mouse.pos.y + dy;
|
||||||
|
|
||||||
ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width);
|
ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width);
|
||||||
ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height);
|
ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
|
||||||
|
|
||||||
ctx.input.events.mouse_move = true;
|
ctx.input.events.mouse_move = true;
|
||||||
}
|
}
|
||||||
|
@ -140,10 +140,10 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
|
|||||||
Point off;
|
Point off;
|
||||||
Rect* cb = &div.children_bounds;
|
Rect* cb = &div.children_bounds;
|
||||||
if (div.scroll.can_x && div.scroll.on_x) {
|
if (div.scroll.can_x && div.scroll.on_x) {
|
||||||
off.x = (short)(cb.w * div.scroll.value_x);
|
off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll.value_x);
|
||||||
}
|
}
|
||||||
if (div.scroll.can_y && div.scroll.on_y) {
|
if (div.scroll.can_y && div.scroll.on_y) {
|
||||||
off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y);
|
off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll.value_y);
|
||||||
}
|
}
|
||||||
Rect view = {
|
Rect view = {
|
||||||
.x = parent.bounds.x + off.x,
|
.x = parent.bounds.x + off.x,
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
module ugui;
|
module ugui;
|
||||||
|
|
||||||
import std::io;
|
import std::io;
|
||||||
|
import std::math;
|
||||||
|
|
||||||
|
// slider element
|
||||||
|
struct ElemSlider {
|
||||||
|
Rect handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Elem SLIDER_DEFAULT = {
|
||||||
|
.type = ETYPE_SLIDER,
|
||||||
|
};
|
||||||
|
|
||||||
/* handle
|
/* handle
|
||||||
* +----+-----+---------------------+
|
* +----+-----+---------------------+
|
||||||
* | |#####| |
|
* | |#####| |
|
||||||
* +----+-----+---------------------+
|
* +----+-----+---------------------+
|
||||||
*/
|
*/
|
||||||
fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
|
<*
|
||||||
|
@require value != null
|
||||||
|
*>
|
||||||
|
fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value)
|
||||||
{
|
{
|
||||||
Id id = label.hash();
|
Id id = label.hash();
|
||||||
|
|
||||||
Elem *parent = ctx.get_parent()!;
|
Elem *parent = ctx.get_parent()!;
|
||||||
Elem *c_elem = ctx.get_elem(id)!;
|
Elem *c_elem = ctx.get_elem(id)!;
|
||||||
@ -17,29 +30,33 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
|
|||||||
ctx.tree.add(id, ctx.active_div)!;
|
ctx.tree.add(id, ctx.active_div)!;
|
||||||
|
|
||||||
// 1. Fill the element fields
|
// 1. Fill the element fields
|
||||||
c_elem.type = ETYPE_SLIDER;
|
if (c_elem.flags.is_new) {
|
||||||
|
*c_elem = SLIDER_DEFAULT;
|
||||||
// if the element is new or the parent was updated then redo layout
|
} else if (c_elem.type != SLIDER_DEFAULT.type) {
|
||||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
return UgError.WRONG_ELEMENT_TYPE?;
|
||||||
// 2. Layout
|
|
||||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
|
||||||
c_elem.slider.handle = Rect{
|
|
||||||
.x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w*(1.0-0.25) * c_elem.slider.value)),
|
|
||||||
.y = c_elem.bounds.y,
|
|
||||||
.w = (short)(c_elem.bounds.w * 0.25),
|
|
||||||
.h = c_elem.bounds.h,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Layout
|
||||||
|
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||||
|
|
||||||
|
// handle width
|
||||||
|
short hw = (short)(c_elem.bounds.w * 0.25);
|
||||||
|
Rect handle = {
|
||||||
|
.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value),
|
||||||
|
.y = c_elem.bounds.y,
|
||||||
|
.w = hw,
|
||||||
|
.h = c_elem.bounds.h,
|
||||||
|
};
|
||||||
|
c_elem.slider.handle = handle;
|
||||||
|
|
||||||
|
Point m = ctx.input.mouse.pos;
|
||||||
c_elem.events = ctx.get_elem_events(c_elem);
|
c_elem.events = ctx.get_elem_events(c_elem);
|
||||||
if (parent.flags.has_focus && c_elem.events.mouse_hover) {
|
|
||||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) {
|
if (parent.flags.has_focus && c_elem.events.mouse_hover &&
|
||||||
short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w);
|
point_in_rect(m, handle) && c_elem.events.mouse_hold) {
|
||||||
float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w);
|
*value = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw);
|
||||||
c_elem.slider.handle.x = x;
|
c_elem.slider.handle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value);
|
||||||
c_elem.slider.value = v;
|
c_elem.events.update = true;
|
||||||
c_elem.events.update = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the slider background and handle
|
// Draw the slider background and handle
|
||||||
@ -63,7 +80,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
|
|||||||
* | |
|
* | |
|
||||||
* +-+
|
* +-+
|
||||||
*/
|
*/
|
||||||
fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
|
fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value)
|
||||||
{
|
{
|
||||||
Id id = label.hash();
|
Id id = label.hash();
|
||||||
|
|
||||||
@ -73,29 +90,34 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
|
|||||||
ctx.tree.add(id, ctx.active_div)!;
|
ctx.tree.add(id, ctx.active_div)!;
|
||||||
|
|
||||||
// 1. Fill the element fields
|
// 1. Fill the element fields
|
||||||
c_elem.type = ETYPE_SLIDER;
|
if (c_elem.flags.is_new) {
|
||||||
|
*c_elem = SLIDER_DEFAULT;
|
||||||
// if the element is new or the parent was updated then redo layout
|
} else if (c_elem.type != SLIDER_DEFAULT.type) {
|
||||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
return UgError.WRONG_ELEMENT_TYPE?;
|
||||||
// 2. Layout
|
|
||||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
|
||||||
c_elem.slider.handle = Rect{
|
|
||||||
.x = c_elem.bounds.x,
|
|
||||||
.y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1.0-0.25) * c_elem.slider.value)),
|
|
||||||
.w = c_elem.bounds.w,
|
|
||||||
.h = (short)(c_elem.bounds.h * 0.25),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Layout
|
||||||
|
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||||
|
|
||||||
|
// handle height
|
||||||
|
short hh = (short)(c_elem.bounds.h * 0.25);
|
||||||
|
Rect handle = {
|
||||||
|
.x = c_elem.bounds.x,
|
||||||
|
.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value),
|
||||||
|
.w = c_elem.bounds.w,
|
||||||
|
.h = hh,
|
||||||
|
};
|
||||||
|
c_elem.slider.handle = handle;
|
||||||
|
|
||||||
|
Point m = ctx.input.mouse.pos;
|
||||||
c_elem.events = ctx.get_elem_events(c_elem);
|
c_elem.events = ctx.get_elem_events(c_elem);
|
||||||
if (parent.flags.has_focus && c_elem.events.mouse_hover) {
|
|
||||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) {
|
if (parent.flags.has_focus && c_elem.events.mouse_hover &&
|
||||||
short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h);
|
point_in_rect(m, handle) &&
|
||||||
float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h);
|
c_elem.events.mouse_hold) {
|
||||||
c_elem.slider.handle.y = y;
|
*value = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh);
|
||||||
c_elem.slider.value = v;
|
c_elem.slider.handle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value);
|
||||||
c_elem.events.update = true;
|
c_elem.events.update = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the slider background and handle
|
// Draw the slider background and handle
|
||||||
@ -106,3 +128,6 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
|
|||||||
|
|
||||||
return c_elem.events;
|
return c_elem.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value);
|
||||||
|
macro float calc_value(ushort off, ushort mouse, ushort dim, ushort slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
|
||||||
|
@ -15,19 +15,12 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text)
|
|||||||
// this resets the flags
|
// this resets the flags
|
||||||
c_elem.type = ETYPE_TEXT;
|
c_elem.type = ETYPE_TEXT;
|
||||||
|
|
||||||
short baseline = (short)ctx.font.ascender;
|
|
||||||
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
|
||||||
short line_gap = (short)ctx.font.linegap;
|
|
||||||
// if the element is new or the parent was updated then redo layout
|
// if the element is new or the parent was updated then redo layout
|
||||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
Rect text_size = ctx.get_text_bounds(text)!;
|
||||||
Rect text_size = ctx.get_text_bounds(text)!;
|
// 2. Layout
|
||||||
|
c_elem.bounds = ctx.position_element(parent, text_size, true);
|
||||||
// 2. Layout
|
// 3. Fill the button specific fields
|
||||||
c_elem.bounds = ctx.position_element(parent, text_size, true);
|
c_elem.text.str = text;
|
||||||
|
|
||||||
// 3. Fill the button specific fields
|
|
||||||
c_elem.text.str = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.push_string(c_elem.bounds, text)!;
|
ctx.push_string(c_elem.bounds, text)!;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user