#include <cairo/cairo.h>
#include <microui.h>
#include <pango/pangocairo.h>
#include <linux/input-event-codes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client-core.h>
#include "shm-buffer.h"
#include "xdg-shell-client-protocol.h"
#define FONT_NAME "monospace 10"
struct pointer_data {
int32_t x, y;
};
struct touch_data {
int32_t x, y;
};
static struct wl_compositor *compositor = NULL;
static struct wl_shm *shm = NULL;
static struct xdg_wm_base *xdg_wm_base = NULL;
static struct wl_surface *surface = NULL;
static struct xdg_surface *xdg_surface = NULL;
static struct xdg_toplevel *xdg_toplevel = NULL;
static float bg[3] = { 90, 95, 100 };
static mu_Context ctx;
static int width = 800;
static int height = 600;
static struct shm_buffer *current;
static struct touch_data touch_slots[10];
static PangoFontDescription *font_desc;
static bool running = true;
static void
update(void)
{
mu_begin(&ctx);
int flags = MU_OPT_NOTITLE;
if (mu_begin_window_ex(&ctx, "", mu_rect(0, 0, width, height), flags)) {
// mu_begin_window_ex isnt resized every time
mu_Container *win = mu_get_current_container(&ctx);
win->rect.w = width;
win->rect.h = height;
if (mu_header_ex(&ctx, "Info", MU_OPT_EXPANDED)) {
char buf[256] = {0};
mu_layout_row(&ctx, 2, (int[]) { 50, -1 }, 0);
mu_label(&ctx, "Size:");
sprintf(buf, "%d, %d", win->rect.w, win->rect.h);
mu_label(&ctx, buf);
}
if (mu_header(&ctx, "Test Buttons")) {
mu_layout_row(&ctx, 3, (int[]) { 100, -100, -1 }, 0);
mu_layout_row(&ctx, 3, NULL, 0);
mu_label(&ctx, "Test buttons 1:");
if (mu_button(&ctx, "Button 1")) {
printf("Pressed button 1\n");
}
if (mu_button(&ctx, "Button 2")) {
printf("Pressed button 2\n");
}
mu_label(&ctx, "Test buttons 2:");
if (mu_button(&ctx, "Button 3")) {
printf("Pressed button 3\n");
}
if (mu_button(&ctx, "Popup")) {
mu_open_popup(&ctx, "Test Popup");
}
if (mu_begin_popup(&ctx, "Test Popup")) {
mu_button(&ctx, "Hello");
mu_button(&ctx, "World");
mu_end_popup(&ctx);
}
}
if (mu_header(&ctx, "Tree and Text")) {
mu_layout_row(&ctx, 2, (int[]) { 180, -1 }, 0);
mu_layout_begin_column(&ctx);
if (mu_begin_treenode(&ctx, "Test 1")) {
if (mu_begin_treenode(&ctx, "Test 1a")) {
mu_label(&ctx, "Hello");
mu_label(&ctx, "world");
mu_end_treenode(&ctx);
}
if (mu_begin_treenode(&ctx, "Test 1b")) {
if (mu_button(&ctx, "Button 1")) {
printf("Pressed button 1");
}
if (mu_button(&ctx, "Button 2")) {
printf("Pressed button 2");
}
mu_end_treenode(&ctx);
}
mu_end_treenode(&ctx);
}
if (mu_begin_treenode(&ctx, "Test 2")) {
mu_layout_row(&ctx, 2, (int[]) { 70, 70 }, 0);
if (mu_button(&ctx, "Button 3")) { printf("Pressed button 3"); }
if (mu_button(&ctx, "Button 4")) { printf("Pressed button 4"); }
if (mu_button(&ctx, "Button 5")) { printf("Pressed button 5"); }
if (mu_button(&ctx, "Button 6")) { printf("Pressed button 6"); }
mu_end_treenode(&ctx);
}
if (mu_begin_treenode(&ctx, "Test 3")) {
static int checks[3] = { 1, 0, 1 };
mu_checkbox(&ctx, "Checkbox 1", &checks[0]);
mu_checkbox(&ctx, "Checkbox 2", &checks[1]);
mu_checkbox(&ctx, "Checkbox 3", &checks[2]);
mu_end_treenode(&ctx);
}
mu_layout_end_column(&ctx);
mu_layout_begin_column(&ctx);
mu_layout_row(&ctx, 1, (int[]) { -1 }, 0);
//mu_layout_row(&ctx, 0, NULL, 0);
//mu_layout_width(&ctx, -1)k;
mu_text(&ctx, "Lorem ipsum dolor sit amet, consectetur adipiscing "
"elit. Maecenas lacinia, sem eu lacinia molestie, mi risus "
"faucibus ipsum, eu varius magna felis a nulla.");
mu_layout_end_column(&ctx);
}
if (mu_header(&ctx, "Background Color")) {
mu_layout_row(&ctx, 2, (int[]) { -78, -1 }, 74);
mu_layout_begin_column(&ctx);
mu_layout_row(&ctx, 2, (int[]) { 46, -1 }, 0);
mu_label(&ctx, "Red:"); mu_slider(&ctx, &bg[0], 0, 255);
mu_label(&ctx, "Green:"); mu_slider(&ctx, &bg[1], 0, 255);
mu_label(&ctx, "Blue:"); mu_slider(&ctx, &bg[2], 0, 255);
mu_layout_end_column(&ctx);
mu_Rect r = mu_layout_next(&ctx);
mu_draw_rect(&ctx, r, mu_color(bg[0], bg[1], bg[2], 255));
char buf[32];
sprintf(buf, "#%02X%02X%02X", (int) bg[0], (int) bg[1], (int) bg[2]);
mu_draw_control_text(&ctx, buf, r, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER);
}
mu_end_window(&ctx);
}
mu_end(&ctx);
}
static void
draw_rect(cairo_t *cr, mu_Rect *rect, mu_Color *color)
{
cairo_save(cr);
cairo_set_source_rgba(cr, color->r / 255.f, color->g / 255.f,
color->b / 255.f, color->a / 255.f);
cairo_rectangle(cr, rect->x, rect->y, rect->w, rect->h);
cairo_fill(cr);
cairo_restore(cr);
}
static void
draw_text(cairo_t *cr, mu_Vec2 *pos, mu_Color *color, const char *str)
{
PangoLayout *layout = pango_cairo_create_layout(cr);
pango_layout_set_text(layout, str, -1);
pango_layout_set_font_description(layout, font_desc);
cairo_save(cr);
cairo_set_source_rgba(cr, color->r / 255.f, color->g / 255.f,
color->b / 255.f, color->a / 255.f);
pango_cairo_update_layout (cr, layout);
int width, height;
pango_layout_get_size (layout, &width, &height);
cairo_move_to (cr, pos->x, pos->y);
pango_cairo_show_layout (cr, layout);
cairo_restore(cr);
g_object_unref(layout);
}
static void
draw_clip(cairo_t *cr, mu_Rect *rect)
{
cairo_save(cr);
cairo_rectangle(cr, rect->x, rect->y, rect->w, rect->h);
cairo_clip(cr);
cairo_restore(cr);
}
static void
draw_icon(cairo_t *cr, mu_Rect *rect, mu_Color *color)
{
mu_Rect r = {
.x = rect->x + (rect->w / 4),
.y = rect->y + (rect->h / 4),
.w = rect->w / 2,
.h = rect->h / 2,
};
draw_rect(cr, &r, color);
}
static void
draw(void)
{
cairo_t *cr = current->cairo;
// clear
cairo_save(cr);
cairo_set_source_rgba(cr, bg[0] / 255.f, bg[1] / 255.f, bg[2] / 255.f, 1.f);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_restore(cr);
mu_Command *cmd = NULL;
while (mu_next_command(&ctx, &cmd)) {
switch (cmd->type) {
case MU_COMMAND_RECT:
draw_rect(cr, &cmd->rect.rect, &cmd->rect.color);
break;
case MU_COMMAND_TEXT:
draw_text(cr, &cmd->text.pos, &cmd->text.color, cmd->text.str);
break;
case MU_COMMAND_CLIP:
draw_clip(cr, &cmd->clip.rect);
break;
case MU_COMMAND_ICON:
draw_icon(cr, &cmd->icon.rect, &cmd->icon.color);
break;
default:
break;
}
}
cairo_surface_flush(current->surface);
}
static void
frame(void)
{
current = shm_buffer_get_next(shm, width, height);
update();
draw();
wl_surface_attach(surface, current->wl_buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
}
static const struct wl_callback_listener frame_listener;
static void
frame_done(void *data, struct wl_callback *callback, uint32_t time)
{
wl_callback_destroy(callback);
callback = wl_surface_frame(surface);
wl_callback_add_listener(callback, &frame_listener, NULL);
frame();
}
static const struct wl_callback_listener frame_listener = {
.done = frame_done,
};
static void
xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
{
xdg_wm_base_pong(xdg_wm_base, serial);
}
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_ping,
};
static void
xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
uint32_t serial)
{
xdg_surface_ack_configure(xdg_surface, serial);
frame();
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_configure,
};
static void
xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t w,
int32_t h, struct wl_array *state)
{
if (w > 0) {
width = w;
}
if (h > 0) {
height = h;
}
}
static void
xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
{
running = false;
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_configure,
.close = xdg_toplevel_close,
};
static void
pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y)
{
struct pointer_data *pointer_data = calloc(1, sizeof(struct pointer_data));
if (pointer_data == NULL) {
fprintf(stderr, "failed to allocate pointer_data");
return;
}
pointer_data->x = wl_fixed_to_int(surface_x);
pointer_data->y = wl_fixed_to_int(surface_y);
wl_pointer_set_user_data(wl_pointer, pointer_data);
}
static void
pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
struct wl_surface *surface)
{
struct pointer_data *pointer_data = wl_pointer_get_user_data(wl_pointer);
free(pointer_data);
}
static void
pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time,
wl_fixed_t surface_x, wl_fixed_t surface_y)
{
struct pointer_data *pointer_data = wl_pointer_get_user_data(wl_pointer);
pointer_data->x = wl_fixed_to_int(surface_x);
pointer_data->y = wl_fixed_to_int(surface_y);
mu_input_mousemove(&ctx, pointer_data->x, pointer_data->y);
}
static void
pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time,
uint32_t axis, int32_t value)
{
float x = 0, y = 0;
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
y = value;
} else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
x = value;
}
mu_input_scroll(&ctx, x, y);
}
static void
pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
int btn;
switch (button) {
case BTN_LEFT:
btn = MU_MOUSE_LEFT;
break;
case BTN_RIGHT:
btn = MU_MOUSE_RIGHT;
break;
case BTN_MIDDLE:
btn = MU_MOUSE_MIDDLE;
break;
default:
fprintf(stderr, "button not handled\n");
return;
}
struct pointer_data *pointer_data = wl_pointer_get_user_data(wl_pointer);
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
mu_input_mousedown(&ctx, pointer_data->x, pointer_data->y, btn);
} else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
mu_input_mouseup(&ctx, pointer_data->x, pointer_data->y, btn);
}
}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_enter,
.leave = pointer_leave,
.motion = pointer_motion,
.button = pointer_button,
.axis = pointer_axis,
};
static void
touch_down (void *data, struct wl_touch *wl_touch, uint32_t serial,
uint32_t time, struct wl_surface *wl_surface, int32_t id,
wl_fixed_t x, wl_fixed_t y)
{
if (id >= 10) {
fprintf(stderr, "id %"PRIi32" not processed\n", id);
return;
}
touch_slots[id].x = wl_fixed_to_int(x);
touch_slots[id].y = wl_fixed_to_int(y);
mu_input_mousedown(&ctx, touch_slots[id].x, touch_slots[id].y,
MU_MOUSE_LEFT);
}
static void
touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time,
int32_t id)
{
if (id >= 10) {
return;
}
mu_input_mouseup(&ctx, touch_slots[id].x, touch_slots[id].y, MU_MOUSE_LEFT);
}
static void
touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id,
wl_fixed_t x, wl_fixed_t y)
{
if (id >= 10) {
fprintf(stderr, "id %"PRIu32" not processed\n", id);
return;
}
touch_slots[id].x = wl_fixed_to_int(x);
touch_slots[id].y = wl_fixed_to_int(y);
mu_input_mousemove(&ctx, touch_slots[id].x, touch_slots[id].y);
}
static void
touch_frame(void *data, struct wl_touch *wl_touch)
{}
static void
touch_cancel(void *data, struct wl_touch *wl_touch)
{}
static void
touch_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major,
wl_fixed_t minor)
{}
static void
touch_orientation(void *data, struct wl_touch *wl_touch, int32_t id,
wl_fixed_t orientation)
{}
static const struct wl_touch_listener touch_listener = {
.down = touch_down,
.up = touch_up,
.motion = touch_motion,
.frame = touch_frame,
.cancel = touch_cancel,
.shape = touch_shape,
.orientation = touch_orientation,
};
static void
seat_capabilities(void *data, struct wl_seat *seat, uint32_t caps)
{
if (caps & WL_SEAT_CAPABILITY_POINTER) {
struct wl_pointer *pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, seat);
}
if (caps & WL_SEAT_CAPABILITY_TOUCH) {
struct wl_touch *touch = wl_seat_get_touch(seat);
wl_touch_add_listener(touch, &touch_listener, seat);
}
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_capabilities,
};
static void
registry_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version)
{
if (strcmp(interface, wl_compositor_interface.name) == 0) {
compositor =
wl_registry_bind(registry, name, &wl_compositor_interface, 4);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
xdg_wm_base =
wl_registry_bind(registry, name, &xdg_wm_base_interface, 2);
xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
struct wl_seat *seat =
wl_registry_bind(registry, name, &wl_seat_interface, 1);
wl_seat_add_listener(seat, &seat_listener, NULL);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
}
}
static void
registry_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static int
text_width(mu_Font font, const char *str, int len)
{
if (len == -1) {
len = strlen(str);
}
PangoFontMetrics *metrics = pango_context_get_metrics(current->pango,
font_desc, NULL);
int w = pango_font_metrics_get_approximate_char_width(metrics) / PANGO_SCALE;
pango_font_metrics_unref(metrics);
return w * len;
}
static int
text_height(mu_Font font)
{
PangoFontMetrics *metrics = pango_context_get_metrics(current->pango,
font_desc, NULL);
int h = pango_font_metrics_get_height(metrics) / PANGO_SCALE;
pango_font_metrics_unref(metrics);
return h;
}
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_connect(NULL);
if (display == NULL) {
fprintf(stderr, "failed to create display\n");
return EXIT_FAILURE;
}
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (compositor == NULL) {
fprintf(stderr, "wl_compositor missing\n");
return EXIT_FAILURE;
} else if (xdg_wm_base == NULL) {
fprintf(stderr, "no xdg_shell support\n");
return EXIT_FAILURE;
}
surface = wl_compositor_create_surface(compositor);
xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
xdg_toplevel_set_title(xdg_toplevel, "wxrc - gui example");
wl_surface_commit(surface);
font_desc = pango_font_description_from_string(FONT_NAME);
mu_init(&ctx);
ctx.text_width = text_width;
ctx.text_height = text_height;
struct wl_callback *callback = wl_surface_frame(surface);
wl_callback_add_listener(callback, &frame_listener, NULL);
while (wl_display_dispatch(display) != -1 && running);
xdg_toplevel_destroy(xdg_toplevel);
xdg_surface_destroy(xdg_surface);
wl_surface_destroy(surface);
wl_compositor_destroy(compositor);
wl_registry_destroy(registry);
wl_display_disconnect(display);
return EXIT_SUCCESS;
}