raven/main.c

385 lines
11 KiB
C
Raw Normal View History

2021-12-16 19:47:39 +02:00
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <pango/pangocairo.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#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;
}