#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; }