commit 1b5357248039af13e8019bc3a447f3c64285d012 Author: hippoz Date: Sat Jan 22 17:51:54 2022 +0200 Initial commit. This commit adds some basic components. Currently, there is an issue with the drawing order of the text. diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..0c58a1c --- /dev/null +++ b/meson.build @@ -0,0 +1,8 @@ +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]) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..0ff8d19 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include +#include +#include + + +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 cr; + Display *dsp; +private: + std::vector 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 cr; +public: + Text(Cairo::RefPtr 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 cr; +public: + Button(Cairo::RefPtr 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; +}