diff --git a/.gitignore b/.gitignore index d6cd342..e2be6ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -ui - +builddir/ diff --git a/Makefile b/Makefile deleted file mode 100644 index af05296..0000000 --- a/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -CFLAGS=-Wall -Wextra -std=c11 -pedantic `pkg-config --cflags cairo pangocairo` -LIBS=`pkg-config --libs cairo pangocairo` -lX11 - -ui: main.c - $(CC) $(CFLAGS) -o ui main.c $(LIBS) - diff --git a/main.c b/main.c deleted file mode 100644 index 0e46e60..0000000 --- a/main.c +++ /dev/null @@ -1,384 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define M_PI 3.14159265358979323846 - -typedef struct Vec2 { - double x, y; -} Vec2; - -typedef struct RGB { - double r, g, b; -} RGB; - -typedef struct uui_row { - Vec2 starting_position, position, spacing, element_size; - bool is_vertical; -} uui_row_t; - -typedef struct uui_context { - int hot_id, active_id; - Vec2 mouse_pos; - bool mouse1_down; - cairo_t *cr; -} uui_context_t; - -typedef enum { - TEXT_CENTER_ALIGN, - TEXT_CENTER_ALIGN_LEFT, -} TextCenterAlign; - -typedef struct uui_text_style { - RGB text_color; - PangoFontDescription *font_description; - TextCenterAlign center_align; -} uui_text_style_t; - -typedef struct uui_button_style { - RGB normal_color, hot_color, active_color; - double border_radius; - int do_fill; - uui_text_style_t *text_style; -} uui_button_style_t; - -Vec2 vec2(double x, double y) { - return (Vec2) { - x, y - }; -} - -RGB rgb(double r, double g, double b) { - return (RGB) { - r, g, b - }; -} - -bool uui_collide_point_rect(Vec2 pos, Vec2 size, Vec2 point) { - return point.x >= pos.x && pos.x + size.x >= point.x && - point.y >= pos.y && pos.y + size.y >= point.y; -} - -void panic(int exit_code, const char *error) { - fputs(error, stderr); - exit(exit_code); -} - -cairo_surface_t *create_x11_surface(int x, int y) { - Display *dsp; - Drawable da; - int screen; - cairo_surface_t *sfc; - - if ((dsp = XOpenDisplay(NULL)) == NULL) - panic(1, "Failed to open display"); - - screen = DefaultScreen(dsp); - da = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp), 0, 0, x, y, 0, 0, 0); - XSelectInput(dsp, da, ButtonPressMask | ButtonReleaseMask | KeyPressMask | PointerMotionMask); - XMapWindow(dsp, da); - - sfc = cairo_xlib_surface_create(dsp, da, DefaultVisual(dsp, screen), x, y); - cairo_xlib_surface_set_size(sfc, x, y); - - return sfc; -} - -void destroy_x11_surface(cairo_surface_t *sfc) { - Display *dsp = cairo_xlib_surface_get_display(sfc); - cairo_surface_destroy(sfc); - XCloseDisplay(dsp); -} - -void uui_handle_event(uui_context_t *uc, XEvent *e) { - char keybuf[8]; - KeySym key; - - switch (e->type) { - case ButtonPress: - if (e->xbutton.button == Button1) - uc->mouse1_down = true; - break; - case ButtonRelease: - if (e->xbutton.button == Button1) - uc->mouse1_down = false; - break; - case KeyPress: - XLookupString(&e->xkey, keybuf, sizeof(keybuf), &key, NULL); - break; - case MotionNotify: - uc->mouse_pos.x = e->xmotion.x; - uc->mouse_pos.y = e->xmotion.y; - break; - } -} - -void uui_draw_rect(cairo_t *cr, Vec2 pos, Vec2 size, double border_radius) -{ - double x = pos.x; - double y = pos.y; - double width = size.x; - double height = size.y; - if (border_radius > 0) { - double aspect = 1.0; - double corner_radius = border_radius; - double radius = corner_radius / aspect; - double degrees = M_PI / 180.0; - - cairo_new_sub_path(cr); - cairo_arc(cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees); - cairo_arc(cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees); - cairo_arc(cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees); - cairo_arc(cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees); - cairo_close_path(cr); - } else { - cairo_rectangle(cr, x, y, width, height); - } -} - -void uui_draw_text(cairo_t *cr, PangoFontDescription *desc, Vec2 pos, const char *text) { - PangoLayout *layout = pango_cairo_create_layout(cr); - pango_layout_set_font_description(layout, desc); - pango_layout_set_text(layout, text, -1); - cairo_move_to(cr, pos.x, pos.y); - pango_cairo_show_layout(cr, layout); - g_object_unref(layout); -} - -void uui_draw_centered_text(cairo_t *cr, PangoFontDescription *desc, TextCenterAlign align, Vec2 rect_pos, Vec2 rect_size, const char *text) { - PangoLayout *layout = pango_cairo_create_layout(cr); - int font_width; - int font_height; - - pango_layout_set_font_description(layout, desc); - pango_layout_set_text(layout, text, -1); - pango_layout_get_pixel_size(layout, &font_width, &font_height); - - double x = rect_pos.x + ((rect_size.x - font_width) / 2); - double y = rect_pos.y + ((rect_size.y - font_height) / 2); - - if (align == TEXT_CENTER_ALIGN_LEFT) { - // center the text only horizontally and add a hardcoded padding to the x position - // TODO: unhardcode the padding - x = rect_pos.x + 4; - } - - cairo_move_to(cr, x, y); - pango_cairo_show_layout(cr, layout); - g_object_unref(layout); -} - -bool uui_do_click_region(uui_context_t *uc, Vec2 pos, Vec2 size, int id) { - if (id != -1) { - /* if the element is hot (hovered) and mouse button 1 is down, it's clicked */ - if (uc->hot_id == id && uc->mouse1_down) { - /* this function only ever returns true once - when the click happens (unless id == -1, in which case it's checked each time, see below) */ - if (uc->active_id == id) { - return false; - } - uc->active_id = id; - return true; - } else { - uc->active_id = -1; - return false; - } - } else { - /* if the user provided -1 as an id, we will do the check independently of the hot/active system */ - return uc->mouse1_down && uui_collide_point_rect(pos, size, uc->mouse_pos); - } -} - -bool uui_do_hover_region(uui_context_t *uc, Vec2 pos, Vec2 size, int id) { - if (id != -1) { - if (uui_collide_point_rect(pos, size, uc->mouse_pos)) { - uc->hot_id = id; - return true; - } else { - uc->hot_id = -1; - return false; - } - } else { - /* if the user provided -1 as an id, we will do the check independently of the hot/active system */ - return uui_collide_point_rect(pos, size, uc->mouse_pos); - } -} - -void uui_do_text(uui_context_t *uc, uui_text_style_t *style, Vec2 pos, const char *text) { - cairo_save(uc->cr); - - cairo_set_source_rgb(uc->cr, style->text_color.r, style->text_color.g, style->text_color.b); - uui_draw_text(uc->cr, style->font_description, pos, text); - cairo_fill(uc->cr); - - cairo_restore(uc->cr); -} - -void uui_do_centered_text(uui_context_t *uc, uui_text_style_t *style, Vec2 pos, Vec2 size, const char *text) { - cairo_save(uc->cr); - - cairo_set_source_rgb(uc->cr, style->text_color.r, style->text_color.g, style->text_color.b); - uui_draw_centered_text(uc->cr, style->font_description, style->center_align, pos, size, text); - cairo_fill(uc->cr); - - cairo_restore(uc->cr); -} - -bool uui_do_button(uui_context_t *uc, uui_button_style_t *style, Vec2 pos, Vec2 size, const char *text, int id) { - bool clicked = false; - - cairo_save(uc->cr); - - if (uui_do_hover_region(uc, pos, size, id)) { - cairo_set_source_rgb(uc->cr, style->hot_color.r, style->hot_color.g, style->hot_color.b); - if (uui_do_click_region(uc, pos, size, id)) { - cairo_set_source_rgb(uc->cr, style->active_color.r, style->active_color.g, style->active_color.b); - clicked = true; - } - } else { - cairo_set_source_rgb(uc->cr, style->normal_color.r, style->normal_color.g, style->normal_color.b); - } - - uui_draw_rect(uc->cr, pos, size, style->border_radius); - - if (style->do_fill) { - cairo_fill(uc->cr); - } else { - cairo_stroke(uc->cr); - } - - if (text != NULL) { - uui_do_centered_text(uc, style->text_style, pos, size, text); - } - - cairo_restore(uc->cr); - - return clicked; -} - -uui_context_t *uui_context_create(cairo_t *cr) { - uui_context_t *uc = malloc(sizeof(uui_context_t)); - uc->mouse_pos = vec2(0, 0); - uc->mouse1_down = false; - uc->cr = cr; - return uc; -} - -void uui_context_destroy(uui_context_t *uc) { - free(uc); -} - -void uui_row_start(uui_row_t *row) { - row->position = row->starting_position; -} - -void uui_row_advance(uui_row_t *row, Vec2 add) { - /* advance the position, respecting the spacing, regardless of direction */ - row->position.x += add.x + row->spacing.x; - row->position.y += add.y + row->spacing.y; -} - -void uui_row_element(uui_row_t *row) { - /* increment position based on element size */ - /* the spacing is added regardless of the direction */ - if (row->is_vertical) { - row->position.y += row->element_size.y; - } else { - row->position.x += row->element_size.x; - } - row->position.x += row->spacing.x; - row->position.y += row->spacing.y; -} - -int main(void) { - cairo_surface_t *sfc = create_x11_surface(800, 600); - cairo_t *cr = cairo_create(sfc); - PangoFontDescription *font_description = pango_font_description_new(); - uui_context_t *uc = uui_context_create(cr); - uui_button_style_t button_style; - uui_text_style_t text_style; - uui_row_t row; - - XEvent e; - - pango_font_description_set_family(font_description, "sans-serif"); - pango_font_description_set_weight(font_description, PANGO_WEIGHT_NORMAL); - pango_font_description_set_absolute_size(font_description, 16 * PANGO_SCALE); - - button_style.border_radius = 8; - button_style.do_fill = 1; - button_style.normal_color = rgb(1.0, 0.0, 0.0); - button_style.hot_color = rgb(0.0, 1.0, 0.0); - button_style.active_color = rgb(0.0, 0.0, 1.0); - button_style.text_style = &text_style; - - text_style.text_color = rgb(1.0, 1.0, 1.0); - text_style.font_description = font_description; - text_style.center_align = TEXT_CENTER_ALIGN_LEFT; - - row.starting_position = vec2(20.0, 20.0); - row.position = vec2(0.0, 0.0); - row.spacing = vec2(20.0, 0.0); - row.element_size = vec2(100.0, 30.0); - row.is_vertical = false; - - int selected_tab = 0; - - for (;;) { - XNextEvent(cairo_xlib_surface_get_display(sfc), &e); - uui_handle_event(uc, &e); - - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_paint(cr); - - uui_row_start(&row); - if (uui_do_button(uc, &button_style, row.position, row.element_size, "one", 0)) { - selected_tab = 0; - } - uui_row_element(&row); - if (uui_do_button(uc, &button_style, row.position, row.element_size, "two", 1)) { - selected_tab = 1; - } - uui_row_element(&row); - if (uui_do_button(uc, &button_style, row.position, row.element_size, "three", 2)) { - selected_tab = 2; - } - uui_row_element(&row); - if (uui_do_button(uc, &button_style, row.position, row.element_size, "four", 3)) { - selected_tab = 3; - } - uui_row_element(&row); - - char *text = "no tab selected"; - - switch (selected_tab) { - case 0: - text = "tab 1"; - break; - case 1: - text = "tab 2"; - break; - case 2: - text = "tab 3"; - break; - case 3: - text = "tab 4"; - break; - } - - uui_do_text(uc, &text_style, row.position, text); - } - - pango_font_description_free(font_description); - cairo_destroy(cr); - destroy_x11_surface(sfc); - uui_context_destroy(uc); - return 0; -} -