A drawing library modeled after Windows GDI
- CreateDC/ReleaseDC
- DrawText
- DrawRect
clang -lxcb -o draggable{,.c} draw.c
For support, email [email protected] or DM me on discord (robert_#4066)
Add badges from somewhere like: shields.io
A drawing library modeled after Windows GDI
clang -lxcb -o draggable{,.c} draw.c
For support, email [email protected] or DM me on discord (robert_#4066)
Add badges from somewhere like: shields.io
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <xcb/xcb.h> | |
#include <stdbool.h> | |
#include "draw.h" | |
xcb_connection_t *c; | |
xcb_screen_t *screen; | |
xcb_gcontext_t gc; | |
xcb_drawable_t parent; | |
xcb_drawable_t swin; | |
int drag_state = 0; | |
uint32_t offset[2]; | |
uint32_t origin[2]; | |
void draw_button(xcb_drawable_t d) { | |
DC surface = CreateDC(c, screen, d); | |
surface.DrawText(surface, "Drag me"); | |
ReleaseDC(surface); | |
//char string[] = "Drag me"; | |
//uint8_t string_len = strlen(string); | |
//xcb_image_text_8(c, string_len, d, gc, 6, 12, string); | |
} | |
xcb_drawable_t make_button(xcb_drawable_t parent, uint32_t width, uint32_t height) { | |
xcb_drawable_t child = xcb_generate_id(c); | |
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK; | |
uint32_t values[3] = { screen->white_pixel, screen->black_pixel, 0 }; | |
values[2] = ( XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | | |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | | |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | | |
XCB_EVENT_MASK_KEY_PRESS); | |
xcb_create_window (c, XCB_COPY_FROM_PARENT, child, parent, 0, 0, width, height, | |
1, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values); | |
origin[0] = 0; | |
origin[1] = 0; | |
xcb_map_window(c, child); | |
draw_button(child); | |
return child; | |
} | |
xcb_drawable_t create_window() { | |
uint32_t mask = 0; | |
uint32_t values[3] = { 0 }; | |
//gc = xcb_generate_id (c); | |
mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_GRAPHICS_EXPOSURES; | |
values[0] = screen->black_pixel; | |
values[1] = screen->white_pixel; | |
//xcb_create_gc (c, gc, screen->root, mask, values); | |
/* create the window */ | |
int handle = xcb_generate_id(c); | |
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; | |
values[0] = screen->white_pixel; | |
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY | | |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_BUTTON_MOTION; | |
xcb_create_window( c, /* connection */ | |
screen->root_depth, /* depth */ | |
handle, /* window Id */ | |
screen->root, /* parent window */ | |
0, 0, /* x, y */ | |
300,300, /* width, height */ | |
0, /* border_width */ | |
XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ | |
screen->root_visual, /* visual */ | |
mask, values); /* masks */ | |
xcb_map_window(c, handle); | |
return handle; | |
} | |
void event_loop(int window) { | |
for ( xcb_generic_event_t *e = xcb_wait_for_event (c) | |
; e | |
; e = xcb_wait_for_event (c)) | |
{ | |
switch (e->response_type & ~0x80) { | |
case XCB_MOTION_NOTIFY: | |
if (((xcb_motion_notify_event_t *)e)->event == swin && drag_state) { | |
xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)e; | |
origin[0] += motion->event_x - offset[0]; | |
origin[1] += motion->event_y - offset[1]; | |
xcb_configure_window(c, swin, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, origin); | |
} | |
xcb_flush(c); | |
break; | |
/* grab the offset from the subwindow's origin */ | |
case XCB_BUTTON_PRESS: | |
if (((xcb_button_press_event_t *) e)->event == swin) { | |
xcb_button_press_event_t *ev = (xcb_button_press_event_t *) e; | |
printf("%d, %d\n", ev->event_x, ev->event_y); | |
offset[0] = ev->event_x; | |
offset[1] = ev->event_y; | |
drag_state = ev->event_y <= 20; | |
} | |
break; | |
case XCB_BUTTON_RELEASE: | |
drag_state = 0; | |
break; | |
case XCB_CONFIGURE_NOTIFY: | |
if (((xcb_configure_notify_event_t *) e)->event == swin) { | |
xcb_configure_notify_event_t *ev = (xcb_configure_notify_event_t *) e; | |
puts("pz"); | |
//origin[0] = ev->x; | |
//origin[1] = ev->y; | |
} | |
break; | |
case XCB_EXPOSE: | |
draw_button(swin); | |
xcb_flush(c); | |
break; | |
case XCB_KEY_PRESS: | |
if (((xcb_key_press_event_t *) e)->detail == 9) { | |
xcb_key_press_event_t *ev = (xcb_key_press_event_t *) e; | |
return; | |
} | |
break; | |
} | |
free (e); | |
} | |
} | |
int main(void) { | |
c = xcb_connect (NULL, NULL); | |
screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; | |
parent = create_window(); | |
swin = make_button(parent, 320, 240); | |
xcb_flush (c); | |
event_loop(parent); | |
return EXIT_SUCCESS; | |
} |
#include <locale.h> | |
#include <stdbool.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <xcb/xcb.h> | |
#include "draw.h" | |
#define MAX(a, b) ((a) > (b) ? (a) : (b)) | |
#define MIN(a, b) ((a) < (b) ? (a) : (b)) | |
#define DEFAULTFN "fixed" | |
bool DrawRect(DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color) { | |
xcb_rectangle_t rect = {x, y, w, h}; | |
uint32_t val[2] = { color, 0 }; | |
xcb_change_gc(dc.c, dc.gc, XCB_GC_FOREGROUND, val); | |
xcb_poly_rectangle(dc.c, dc.canvas, dc.gc, 1, &rect); | |
return true; | |
} | |
bool FillRect(DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color) { | |
xcb_rectangle_t rect = {x, y, w, h}; | |
uint32_t val[2] = { color, 0 }; | |
xcb_change_gc(dc.c, dc.gc, XCB_GC_FOREGROUND, val); | |
xcb_poly_fill_rectangle(dc.c, dc.canvas, dc.gc, 1, &rect); | |
return true; | |
} | |
void ReleaseDC(DC dc) { | |
// if (dc.font.xfont) { | |
// XFreeFont(dc.dpy, dc.font.xfont); | |
// } | |
if(dc.canvas) { | |
xcb_free_pixmap(dc.c, dc.canvas); | |
} | |
xcb_free_gc(dc.c, dc.gc); | |
} | |
uint32_t GetColor(DC dc, const char *colstr){ | |
unsigned int r = 0, g = 0, b = 0; | |
uint32_t pixel = 0; | |
xcb_alloc_color_reply_t *reply; | |
/* convert 24 bit color values to 48 bit color values | |
* with assistance from bspwm by Baskerville | |
* https://github.com/baskerville/bspwm */ | |
fprintf(stderr, "color string: %s\n", colstr+1); | |
if (sscanf(colstr+1, "%2x%2x%2x", &r, &g, &b) == 3) { | |
// convert color to 48 bit 0x23 * 0x101 = 0x2323 | |
fprintf(stderr, "r: %x, g: %x, b: %x\n", r, g, b); | |
r *= 0x101; | |
g *= 0x101; | |
b *= 0x101; | |
if((reply = xcb_alloc_color_reply(dc.c, xcb_alloc_color(dc.c, dc.screen->default_colormap, r, g, b), NULL))) { | |
pixel = reply->pixel; | |
fprintf(stderr, "GetColor success\n"); | |
free(reply); | |
} | |
} | |
return pixel; | |
} | |
static bool | |
TextFontInit(DC dc, const char* fontstr) { | |
bool response = false; | |
xcb_font_t font; | |
xcb_void_cookie_t cookie; | |
xcb_generic_error_t *error; | |
xcb_query_font_cookie_t fookie; | |
xcb_query_font_reply_t *reply; | |
font = xcb_generate_id(dc.c); | |
cookie = xcb_open_font_checked(dc.c, font, strlen(fontstr), fontstr); | |
if(!((error = xcb_request_check(dc.c, cookie)))) { | |
fookie = xcb_query_font(dc.c, font); | |
if ((reply = xcb_query_font_reply(dc.c, fookie, NULL))) { | |
dc.font.ascent = reply->font_ascent; | |
dc.font.descent = reply->font_descent; | |
dc.font.width = reply->max_bounds.character_width; | |
dc.font.xfont = font; | |
response = true; | |
} | |
} | |
fprintf(stderr, "cannot load font"); | |
free(error); | |
return response; | |
} | |
void TextFontLoad(DC dc, const char *fontstr) { | |
if(!TextFontInit(dc, fontstr ? fontstr : DEFAULTFN)) { | |
if(fontstr != NULL) { | |
fprintf(stderr, "cannot load font '%s'\n", fontstr); | |
} | |
else if(fontstr == NULL || !TextFontInit(dc, DEFAULTFN)) { | |
fprintf(stderr, "cannot load font '%s'\n", DEFAULTFN); | |
} | |
} | |
else { | |
dc.font.height = dc.font.ascent + dc.font.descent; | |
} | |
} | |
bool Map(DC dc, uint16_t w, uint16_t h) { | |
xcb_void_cookie_t cookie = | |
xcb_copy_area_checked(dc.c, dc.canvas, dc.window, dc.gc, 0, 0, 0, 0, w, h); | |
xcb_generic_error_t *error; | |
bool result = false; | |
if ((error = xcb_request_check(dc.c, cookie))) { | |
fprintf(stderr, "Map failure\n"); | |
free(error); | |
} | |
else { | |
fprintf(stderr, "Map success\n"); | |
result = true; | |
} | |
xcb_flush(dc.c); | |
return result;; | |
} | |
void Resize(DC dc, uint16_t w, uint16_t h) { | |
xcb_generic_error_t *error; | |
xcb_void_cookie_t cookie; | |
dc.w = w; | |
dc.h = h; | |
dc.canvas = xcb_generate_id(dc.c); | |
cookie = xcb_create_pixmap_checked(dc.c, dc.screen->root_depth, dc.canvas, dc.screen->root, w, h); | |
if ((error = xcb_request_check(dc.c, cookie))) { | |
fprintf(stderr, "Resize failure\n"); | |
free(error); | |
} | |
else | |
fprintf(stderr, "Resize success\n"); | |
} | |
bool DrawTextCW(DC dc, const char *text, size_t n, color_t col) { | |
xcb_void_cookie_t cookie; | |
xcb_generic_error_t *error; | |
bool result = false; | |
int xp = dc.x + dc.font.height/2, | |
yp = dc.y + dc.font.ascent+1; | |
uint32_t val[3] = { FG(dc, col), BG(dc, col), dc.font.xfont }; | |
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; | |
cookie = xcb_change_gc_checked(dc.c, dc.gc, mask, val); | |
if(((error = xcb_request_check(dc.c, cookie)))) { | |
fprintf(stderr, "errror could not complete DrawText request"); | |
free(error); | |
} | |
else { | |
cookie = xcb_image_text_8_checked(dc.c, n, dc.canvas, dc.gc, xp, yp, text); | |
if(((error = xcb_request_check(dc.c, cookie)))) { | |
fprintf(stderr, "errror could not complete DrawText request"); | |
free(error); | |
} | |
else { | |
result = true; | |
} | |
} | |
return result; | |
} | |
uint32_t DrawTextW(DC dc, const char *text, size_t len) { | |
//xcb_query_text_extents_cookie_t cookie; | |
//xcb_query_text_extents_reply_t *reply; | |
int32_t result = 0, width = len; | |
const char* string = strndup(text, len); | |
/* | |
xcb_char2b_t *string; | |
string = calloc(1, sizeof(xcb_char2b_t)); | |
string->byte1 = 0; | |
string->byte2 = 0; | |
memcpy(&string->byte2, text, sizeof(char)); | |
cookie = xcb_query_text_extents(dc.c, dc.font.xfont, len, string); | |
if ((reply = xcb_query_text_extents_reply(dc.c, cookie, NULL))) { | |
width = reply->overall_width; | |
free(reply); | |
result = width; | |
} | |
*/ | |
xcb_image_text_8(dc.c, len, dc.window, dc.gc, 12, 12, string); | |
return 0; | |
} | |
bool DrawText(DC dc, const char *text) { | |
return DrawTextW(dc, text, strlen(text)) + dc.font.height; | |
} | |
bool DrawTextColor(DC dc, const char *text, color_t col) { | |
char buf[BUFSIZ]; | |
size_t n = strlen(text), | |
mn = MIN(n, sizeof buf); | |
/** | |
* shorten text if necessary | |
*/ | |
for (mn = MIN(n, sizeof buf); mn > 0; mn--) { | |
DrawTextW(dc, text, mn); // + dc.font.height / 2 > dc.w); | |
} | |
memcpy(buf, text, mn); | |
if(mn < n) { | |
for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.'); | |
} | |
// DrawRect(dc, 0, 0, dc.w, dc.h, true, BG(dc, col)); | |
return DrawTextCW(dc, buf, mn, col); | |
} | |
DC CreateDC(xcb_connection_t *connection, xcb_screen_t* screen, xcb_window_t window) { | |
DC dc = { | |
.c = connection, | |
.gc = xcb_generate_id(connection), | |
.screen = screen, | |
.mask = XCB_GC_LINE_STYLE | XCB_GC_CAP_STYLE | XCB_GC_JOIN_STYLE, | |
.window = window, | |
.DrawRect = DrawRect, | |
.FillRect = FillRect, | |
.GetColor = GetColor, | |
.LoadFont = TextFontInit, | |
.DrawText = DrawText, | |
.DrawTextW = DrawTextW, | |
.DrawTextCW = DrawTextCW, | |
}; | |
uint32_t values[3] = { screen->black_pixel, screen->white_pixel, 0 }; | |
uint32_t rect[3] = { XCB_LINE_STYLE_SOLID, XCB_CAP_STYLE_BUTT, XCB_JOIN_STYLE_MITER }; | |
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK; | |
xcb_create_gc(connection, dc.gc, dc.screen->root, mask, rect); | |
return dc; | |
} |
#define FG(dc, col) ((col)[(dc).invert ? ColBG : ColFG]) | |
#define BG(dc, col) ((col)[(dc).invert ? ColFG : ColBG]) | |
enum { ColBG, ColFG, ColBorder, ColLast }; | |
typedef uint32_t color_t [ColLast]; | |
typedef struct DC { | |
uint32_t x, y, w, h; | |
uint8_t invert; | |
xcb_connection_t *c; | |
uint32_t mask; | |
xcb_screen_t *screen; | |
xcb_window_t window; | |
xcb_gcontext_t gc; | |
xcb_pixmap_t canvas; | |
struct { | |
uint16_t ascent; | |
uint16_t descent; | |
uint32_t height; | |
uint32_t width; | |
xcb_font_t xfont; | |
} font; | |
bool (*DrawRect)(struct DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color); | |
bool (*FillRect)(struct DC dc, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color); | |
bool (*Text)(struct DC dc, const char *text, color_t col); | |
uint32_t (*GetColor)(struct DC dc, const char *colstr); | |
bool (*InitFont)(struct DC dc, const char *fontstr); | |
bool (*LoadFont)(struct DC dc, const char *fontstr); | |
bool (*MapDC)(struct DC dc, uint16_t w, uint16_t h); | |
bool (*Resize)(struct DC dc, uint16_t w, uint16_t h); | |
bool (*DrawText)(struct DC dc, const char *text); | |
uint32_t (*DrawTextW)(struct DC dc, const char *text, size_t len); | |
bool (*DrawTextCW)(struct DC dc, const char *text, size_t len, color_t col); | |
} DC, *HDC; /* draw context */ | |
DC CreateDC(xcb_connection_t *connection, xcb_screen_t* screen, xcb_window_t window); | |
void ReleaseDC(DC dc); |