Compare commits
No commits in common. "f3ab6c72a1ddc55e02d8d81c553f301398d07906" and "dd2c24c23f7e605dffd358ee4b390d8bf17a9547" have entirely different histories.
f3ab6c72a1
...
dd2c24c23f
5 changed files with 392 additions and 351 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
builddir/
|
ui
|
||||||
|
|
||||||
|
|
6
Makefile
Normal file
6
Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
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)
|
||||||
|
|
384
main.c
Normal file
384
main.c
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
project('blit', 'cpp')
|
|
||||||
|
|
||||||
|
|
||||||
cairomm_dep = dependency('cairomm-1.0')
|
|
||||||
pangocairo_dep = dependency('pangocairo')
|
|
||||||
xlib_dep = dependency('x11')
|
|
||||||
|
|
||||||
executable('blit_app', './src/main.cpp', dependencies : [cairomm_dep, xlib_dep, pangocairo_dep])
|
|
342
src/main.cpp
342
src/main.cpp
|
@ -1,342 +0,0 @@
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cairomm-1.0/cairomm/xlib_surface.h>
|
|
||||||
#include <cairomm-1.0/cairomm/context.h>
|
|
||||||
#include <cairomm-1.0/cairomm/surface.h>
|
|
||||||
#include <pango/pangocairo.h>
|
|
||||||
|
|
||||||
|
|
||||||
void panic(int exit_code, const char *message) {
|
|
||||||
fputs(message, stderr);
|
|
||||||
exit(exit_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Blit {
|
|
||||||
|
|
||||||
bool collide_point_rect(double x, double y, double w, double h, double x1, double y1) {
|
|
||||||
return x1 >= x && x + w >= x1 &&
|
|
||||||
y1 >= y && y + h >= y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum TextAlign {
|
|
||||||
TEXT_ALIGN_NONE,
|
|
||||||
TEXT_ALIGN_CENTER_LEFT,
|
|
||||||
TEXT_ALIGN_CENTER
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class RGB {
|
|
||||||
public:
|
|
||||||
double r, g, b;
|
|
||||||
public:
|
|
||||||
RGB(double r, double g, double b) : r(r), g(g), b(b) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class BaseElement {
|
|
||||||
public:
|
|
||||||
BaseElement() {}
|
|
||||||
virtual ~BaseElement() {}
|
|
||||||
|
|
||||||
bool should_get_x_events = true;
|
|
||||||
virtual void draw() = 0;
|
|
||||||
virtual void handle_event(XEvent *e) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Window {
|
|
||||||
public:
|
|
||||||
Cairo::RefPtr<Cairo::Context> cr;
|
|
||||||
Display *dsp;
|
|
||||||
private:
|
|
||||||
std::vector<Blit::BaseElement *> m_attached_elements;
|
|
||||||
public:
|
|
||||||
Window(int x, int y)
|
|
||||||
{
|
|
||||||
Drawable da;
|
|
||||||
int screen;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
auto x_surface = Cairo::XlibSurface::create(
|
|
||||||
dsp,
|
|
||||||
da,
|
|
||||||
DefaultVisual(dsp, screen),
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
cr = Cairo::Context::create(x_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
void attach_element_init(Blit::BaseElement *element) {
|
|
||||||
m_attached_elements.push_back(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
void attach_element(Blit::BaseElement *element) {
|
|
||||||
m_attached_elements.push_back(element);
|
|
||||||
element->draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
void redraw_all() {
|
|
||||||
for (Blit::BaseElement * element : m_attached_elements) {
|
|
||||||
element->draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_x_event(XEvent *e) {
|
|
||||||
for (Blit::BaseElement * element : m_attached_elements) {
|
|
||||||
if (element->should_get_x_events)
|
|
||||||
element->handle_event(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Text : public BaseElement {
|
|
||||||
public:
|
|
||||||
PangoFontDescription *font_description;
|
|
||||||
bool should_get_x_events = false;
|
|
||||||
Blit::TextAlign text_align = Blit::TextAlign::TEXT_ALIGN_NONE;
|
|
||||||
double x, y, box_x, box_y, box_w, box_h = 0;
|
|
||||||
std::string text;
|
|
||||||
Blit::RGB fg;
|
|
||||||
|
|
||||||
Cairo::RefPtr<Cairo::Context> cr;
|
|
||||||
public:
|
|
||||||
Text(Cairo::RefPtr<Cairo::Context> cr, std::string& text, PangoFontDescription *font_description) :
|
|
||||||
cr(cr),
|
|
||||||
text(text),
|
|
||||||
font_description(font_description),
|
|
||||||
fg(0.0, 0.0, 0.0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual ~Text() {}
|
|
||||||
|
|
||||||
void draw() {
|
|
||||||
cr->save();
|
|
||||||
cr->set_source_rgb(fg.r, fg.g, fg.b);
|
|
||||||
|
|
||||||
if (text_align == Blit::TextAlign::TEXT_ALIGN_NONE) {
|
|
||||||
PangoLayout *layout = pango_cairo_create_layout(cr->cobj());
|
|
||||||
pango_layout_set_font_description(layout, font_description);
|
|
||||||
pango_layout_set_text(layout, text.c_str(), -1);
|
|
||||||
cr->move_to(x, y);
|
|
||||||
pango_cairo_show_layout(cr->cobj(), layout);
|
|
||||||
g_object_unref(layout);
|
|
||||||
} else if (text_align == Blit::TextAlign::TEXT_ALIGN_CENTER_LEFT) {
|
|
||||||
PangoLayout *layout = pango_cairo_create_layout(cr->cobj());
|
|
||||||
int font_width;
|
|
||||||
int font_height;
|
|
||||||
|
|
||||||
pango_layout_set_font_description(layout, font_description);
|
|
||||||
pango_layout_set_text(layout, text.c_str(), -1);
|
|
||||||
pango_layout_get_pixel_size(layout, &font_width, &font_height);
|
|
||||||
|
|
||||||
double x = box_x + ((box_w - font_width) / 2);
|
|
||||||
double y = box_y + ((box_h - font_height) / 2);
|
|
||||||
|
|
||||||
// TODO: unhardcode the padding
|
|
||||||
x = box_x + 4;
|
|
||||||
|
|
||||||
cr->move_to(x, y);
|
|
||||||
pango_cairo_show_layout(cr->cobj(), layout);
|
|
||||||
g_object_unref(layout);
|
|
||||||
} else if (text_align == Blit::TextAlign::TEXT_ALIGN_CENTER) {
|
|
||||||
PangoLayout *layout = pango_cairo_create_layout(cr->cobj());
|
|
||||||
int font_width;
|
|
||||||
int font_height;
|
|
||||||
|
|
||||||
pango_layout_set_font_description(layout, font_description);
|
|
||||||
pango_layout_set_text(layout, text.c_str(), -1);
|
|
||||||
pango_layout_get_pixel_size(layout, &font_width, &font_height);
|
|
||||||
|
|
||||||
double x = box_x + ((box_w - font_width) / 2);
|
|
||||||
double y = box_y + ((box_h - font_height) / 2);
|
|
||||||
|
|
||||||
cr->move_to(x, y);
|
|
||||||
pango_cairo_show_layout(cr->cobj(), layout);
|
|
||||||
g_object_unref(layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
cr->fill();
|
|
||||||
cr->restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_event(XEvent *e) {}
|
|
||||||
|
|
||||||
void set_x(double val) {
|
|
||||||
x = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_y(double val) {
|
|
||||||
y = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_box_x(double val) {
|
|
||||||
box_x = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_box_y(double val) {
|
|
||||||
box_y = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_box_w(double val) {
|
|
||||||
box_w = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_box_h(double val) {
|
|
||||||
box_h = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_text(std::string& text) {
|
|
||||||
text = text;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_text_align(Blit::TextAlign val) {
|
|
||||||
text_align = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_current_fg(Blit::RGB val) {
|
|
||||||
fg = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Button : public BaseElement {
|
|
||||||
public:
|
|
||||||
bool should_get_x_events = true;
|
|
||||||
double x, y, w, h, border_radius = 0;
|
|
||||||
Blit::RGB current_bg, bg, sel_bg;
|
|
||||||
|
|
||||||
bool is_hot, is_active = false;
|
|
||||||
|
|
||||||
Cairo::RefPtr<Cairo::Context> cr;
|
|
||||||
public:
|
|
||||||
Button(Cairo::RefPtr<Cairo::Context> cr) :
|
|
||||||
should_get_x_events(true),
|
|
||||||
current_bg(0.0, 0.0, 0.0),
|
|
||||||
sel_bg(0.0, 0.0, 0.0),
|
|
||||||
bg(0.0, 0.0, 0.0),
|
|
||||||
cr(cr)
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual ~Button() {}
|
|
||||||
|
|
||||||
void draw() {
|
|
||||||
cr->save();
|
|
||||||
cr->set_source_rgb(current_bg.r, current_bg.g, current_bg.b);
|
|
||||||
if (border_radius > 0) {
|
|
||||||
double aspect = 1.0;
|
|
||||||
double radius = border_radius / aspect;
|
|
||||||
double degrees = M_PI / 180.0;
|
|
||||||
|
|
||||||
cr->begin_new_sub_path();
|
|
||||||
cr->arc(x + w - radius, y + radius, radius, -90 * degrees, 0 * degrees);
|
|
||||||
cr->arc(x + w - radius, y + h - radius, radius, 0 * degrees, 90 * degrees);
|
|
||||||
cr->arc(x + radius, y + h - radius, radius, 90 * degrees, 180 * degrees);
|
|
||||||
cr->arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
|
|
||||||
cr->close_path();
|
|
||||||
} else {
|
|
||||||
cr->rectangle(x, y, w, h);
|
|
||||||
}
|
|
||||||
cr->fill();
|
|
||||||
cr->restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_event(XEvent *e) {
|
|
||||||
char keybuf[8];
|
|
||||||
KeySym key;
|
|
||||||
|
|
||||||
switch (e->type) {
|
|
||||||
case MotionNotify:
|
|
||||||
bool is_hot_now = Blit::collide_point_rect(x, y, w, h, e->xmotion.x, e->xmotion.y);
|
|
||||||
if (!is_hot && is_hot_now) { // just became hot
|
|
||||||
set_current_bg(sel_bg);
|
|
||||||
is_hot = true;
|
|
||||||
} else if (is_hot && !is_hot_now) { // just became not hot
|
|
||||||
set_current_bg(bg);
|
|
||||||
is_hot = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_x(double val) {
|
|
||||||
x = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_y(double val) {
|
|
||||||
y = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_w(double val) {
|
|
||||||
w = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_h(double val) {
|
|
||||||
h = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_border_radius(double val) {
|
|
||||||
border_radius = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
void set_current_bg(Blit::RGB val) {
|
|
||||||
current_bg = val;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
std::string text = "click me now";
|
|
||||||
XEvent e;
|
|
||||||
PangoFontDescription *font_description = pango_font_description_new();
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Blit::Window window(800, 600);
|
|
||||||
Blit::Button button(window.cr);
|
|
||||||
Blit::Text button_text(window.cr, text, font_description);
|
|
||||||
|
|
||||||
button.x = 20.0;
|
|
||||||
button.y = 20.0;
|
|
||||||
button.w = 100.0;
|
|
||||||
button.h = 30.0;
|
|
||||||
button.border_radius = 0.0;
|
|
||||||
button.bg = Blit::RGB(1.0, 0.0, 0.0);
|
|
||||||
button.sel_bg = Blit::RGB(0.0, 1.0, 0.0);
|
|
||||||
|
|
||||||
button_text.x = button.x;
|
|
||||||
button_text.y = button.y;
|
|
||||||
button_text.box_x = button.x;
|
|
||||||
button_text.box_y = button.y;
|
|
||||||
button_text.box_w = button.w;
|
|
||||||
button_text.box_h = button.h;
|
|
||||||
button_text.fg = Blit::RGB(1.0, 1.0, 1.0);
|
|
||||||
button_text.text_align = Blit::TextAlign::TEXT_ALIGN_CENTER;
|
|
||||||
|
|
||||||
window.attach_element(&button);
|
|
||||||
window.attach_element(&button_text);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
XNextEvent(window.dsp, &e);
|
|
||||||
window.handle_x_event(&e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in a new issue