toaruos/userspace/gui/basic/draw.c

511 lines
15 KiB
C

/*
* draw
*
* Windowed graphical drawing tool.
* Simple painting application.
*
* This is also the playground for the work-in-progress
* ToaruToolKit GUI toolkit.
*/
#include <stdlib.h>
#include "lib/yutani.h"
#include "gui/ttk/ttk.h"
#include "lib/list.h"
/* XXX TOOLKIT FUNCTIONS */
gfx_context_t * ctx;
yutani_window_t * wina;
yutani_t * yctx;
/* Active TTK window XXX */
static yutani_window_t * ttk_window = NULL;
/* TTK Window's objects XXX */
static list_t * ttk_objects = NULL;
#define TTK_BUTTON_TYPE 0x00000001
#define TTK_RAW_SURFACE_TYPE 0x00000002
#define TTK_BUTTON_STATE_NORMAL 0
#define TTK_BUTTON_STATE_DOWN 1
cairo_surface_t * internal_surface;
/*
* Core TTK GUI object
*/
typedef struct {
uint32_t type; /* Object type indicator (for introspection) */
int32_t x; /* Coordinates */
int32_t y;
int32_t width; /* Sizes */
int32_t height;
void (*render_func)(void *, cairo_t * cr); /* (Internal) function to render the object */
void (*click_callback)(void *, struct yutani_msg_window_mouse_event *); /* Callback function for clicking */
} ttk_object;
/* TTK Button */
typedef struct {
ttk_object _super; /* Parent type (Object -> Button) */
char * title; /* Button text */
uint32_t fill_color; /* Fill color */
uint32_t fore_color; /* Text color */
int button_state;
} ttk_button;
typedef struct {
ttk_object _super;
gfx_context_t * surface;
} ttk_raw_surface;
void ttk_render_button(void * s, cairo_t * cr) {
ttk_object * self = (ttk_object *)s;
if (((ttk_button *)self)->button_state == TTK_BUTTON_STATE_DOWN) {
_ttk_draw_button_select(cr, self->x, self->y, self->width, self->height, ((ttk_button *)self)->title);
} else {
_ttk_draw_button(cr, self->x, self->y, self->width, self->height, ((ttk_button *)self)->title);
}
#if 0
/* Fill the button */
for (uint16_t y = self->y + 1; y < self->y + self->height; y++) {
draw_line(ctx, self->x, self->x + self->width, y, y, ((ttk_button *)self)->fill_color);
}
/* Then draw the border */
uint32_t border_color = rgb(0,0,0);
draw_line(ctx, self->x, self->x + self->width, self->y, self->y, border_color);
draw_line(ctx, self->x, self->x, self->y, self->y + self->height, border_color);
draw_line(ctx, self->x + self->width, self->x + self->width, self->y, self->y + self->height, border_color);
draw_line(ctx, self->x, self->x + self->width, self->y + self->height, self->y + self->height, border_color);
/* button-specific stuff */
uint32_t w = draw_string_width(((ttk_button * )self)->title);
uint16_t offset = (self->width - w) / 2;
draw_string(ctx, self->x + offset, self->y + self->height - 3, ((ttk_button *)self)->fore_color, ((ttk_button * )self)->title);
#endif
}
void ttk_render_raw_surface(void * s, cairo_t * cr) {
ttk_object * self = (ttk_object *)s;
gfx_context_t * surface = ((ttk_raw_surface *)self)->surface;
{
cairo_save(cr);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, surface->width);
cairo_surface_t * internal_surface = cairo_image_surface_create_for_data(surface->backbuffer, CAIRO_FORMAT_ARGB32, surface->width, surface->height, stride);
cairo_set_source_surface(cr, internal_surface, self->x, self->y);
cairo_paint(cr);
cairo_surface_destroy(internal_surface);
cairo_restore(cr);
}
}
ttk_button * ttk_button_new(char * title, void (*callback)(void *, struct yutani_msg_window_mouse_event *)) {
ttk_button * out = malloc(sizeof(ttk_button));
out->title = title;
out->fill_color = rgb(100,100,100);
out->button_state = TTK_BUTTON_STATE_NORMAL;
/* Standard */
ttk_object * obj = (ttk_object *)out;
obj->click_callback = callback;
obj->render_func = ttk_render_button;
obj->type = TTK_BUTTON_TYPE;
obj->x = 0;
obj->y = 0;
obj->width = 20;
obj->height = 20;
list_insert(ttk_objects, obj);
return out;
}
static cairo_surface_t * close_button_sprite;
void ttk_render_decor_button_close(void * s, cairo_t * cr) {
cairo_save(cr);
cairo_set_source_rgb(cr, 244.0, 244.0, 244.0);
double x = ((ttk_object *)s)->x;
double y = ((ttk_object *)s)->y;
cairo_set_source_surface(cr, close_button_sprite, x + 1, y + 1);
cairo_paint(cr);
cairo_restore(cr);
}
ttk_button * ttk_decor_button_close(void (*callback)(void *, struct yutani_msg_window_mouse_event *)) {
if (!close_button_sprite) {
close_button_sprite = cairo_image_surface_create_from_png("/usr/share/ttk/common/button-close.png"); /* TTK_PATH ? something less dumb? */
}
ttk_button * out = ttk_button_new("Close" /* For future tooltips */, callback);
((ttk_object *)out)->render_func = ttk_render_decor_button_close;
((ttk_object *)out)->width = 10;
((ttk_object *)out)->height = 10;
return out;
}
ttk_raw_surface * ttk_raw_surface_new(int width, int height) {
ttk_raw_surface * out = malloc(sizeof(ttk_raw_surface));
ttk_object * obj = (ttk_object *)out;
out->surface = malloc(sizeof(gfx_context_t));
out->surface->width = width;
out->surface->height = height;
out->surface->depth = 32;
out->surface->buffer = malloc(sizeof(uint32_t) * width * height);
out->surface->backbuffer = out->surface->buffer;
draw_fill(out->surface, rgb(255,255,255));
obj->width = width;
obj->height = height;
obj->x = 10;
obj->y = 10;
obj->click_callback = NULL;
obj->type = TTK_RAW_SURFACE_TYPE;
obj->render_func = ttk_render_raw_surface;
list_insert(ttk_objects, obj);
return out;
}
/*
* Reposition a TTK object
*/
void ttk_position(ttk_object * obj, int x, int y, int width, int height) {
obj->x = x;
obj->y = y;
obj->width = width;
obj->height = height;
}
int ttk_within(ttk_object * obj, struct yutani_msg_window_mouse_event * evt) {
if (evt->new_x >= obj->x && evt->new_x < obj->x + obj->width &&
evt->new_y >= obj->y && evt->new_y < obj->y + obj->height) {
return 1;
}
return 0;
}
void ttk_check_click(struct yutani_msg_window_mouse_event * evt) {
if (evt->command == YUTANI_MOUSE_EVENT_CLICK) {
foreach(node, ttk_objects) {
ttk_object * obj = (ttk_object *)node->value;
if (ttk_within(obj, evt)) {
if (obj->click_callback) {
obj->click_callback(obj, evt);
}
}
}
}
}
void ttk_render() {
/* XXX */
ttk_window_t _window;
ttk_window_t * window = &_window;
yutani_window_t _wina;
_wina.buffer = wina->buffer;
_wina.width = wina->width;
_wina.height = wina->height;
_wina.focused = wina->focused;
window->core_context = ctx;
window->core_window = &_wina;
window->width = ctx->width; // - decor_width();
window->height = ctx->height; //- decor_height();
window->off_x = 0; //decor_left_width;
window->off_y = 0; //decor_top_height;
window->title = "Draw!";
draw_fill(ctx, rgb(TTK_BACKGROUND_DEFAULT));
ttk_redraw_borders(window);
{
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, window->core_window->width);
cairo_surface_t * core_surface = cairo_image_surface_create_for_data(window->core_context->backbuffer, CAIRO_FORMAT_ARGB32, window->core_window->width, window->core_window->height, stride);
cairo_t * cr_main = cairo_create(core_surface);
/* TODO move this surface to a ttk_frame_t or something; GUIs man, go look at some Qt or GTK APIs! */
cairo_surface_t * internal_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, window->width, window->height);
cairo_t * cr = cairo_create(internal_surface);
foreach(node, ttk_objects) {
ttk_object * obj = (ttk_object *)node->value;
if (obj->render_func) {
obj->render_func(obj, cr);
}
}
/* Paint the window's internal surface onto the backbuffer */
cairo_set_source_surface(cr_main, internal_surface, (double)window->off_x, (double)window->off_y);
cairo_paint(cr_main);
cairo_surface_flush(internal_surface);
cairo_destroy(cr);
cairo_surface_destroy(internal_surface);
/* In theory, we don't actually want to destroy much of any of this; maybe the cairo_t */
cairo_surface_flush(core_surface);
cairo_destroy(cr_main);
cairo_surface_destroy(core_surface);
}
flip(window->core_context);
yutani_flip(yctx, wina);
}
void setup_ttk(yutani_window_t * window) {
ttk_window = window;
ttk_objects = list_create();
init_shmemfonts();
}
uint32_t drawing_color = 0;
uint16_t quit = 0;
ttk_button * button_red;
ttk_button * button_green;
ttk_button * button_blue;
ttk_button * close_button;
ttk_button * button_thick;
ttk_button * button_thin;
ttk_raw_surface * drawing_surface;
int thick = 0;
static void set_color(void * button, struct yutani_msg_window_mouse_event * event) {
ttk_button * self = (ttk_button *)button;
if (button_blue != self) button_blue->button_state = TTK_BUTTON_STATE_NORMAL;
if (button_red != self) button_red->button_state = TTK_BUTTON_STATE_NORMAL;
if (button_green != self) button_green->button_state = TTK_BUTTON_STATE_NORMAL;
self->button_state = TTK_BUTTON_STATE_DOWN;
drawing_color = self->fill_color;
ttk_render();
}
static void quit_app(void * button, struct yutani_msg_window_mouse_event * event) {
quit = 1;
}
static void set_thickness_thick(void * button, struct yutani_msg_window_mouse_event * event) {
#if 0
button_thick->fill_color = rgb(127,127,127);
button_thick->fore_color = rgb(255,255,255);
button_thin->fill_color = rgb(40,40,40);
button_thin->fore_color = rgb(255,255,255);
#endif
button_thick->button_state = TTK_BUTTON_STATE_DOWN;
button_thin->button_state = TTK_BUTTON_STATE_NORMAL;
thick = 1;
ttk_render();
}
static void set_thickness_thin(void * button, struct yutani_msg_window_mouse_event * event) {
#if 0
button_thin->fill_color = rgb(127,127,127);
button_thin->fore_color = rgb(255,255,255);
button_thick->fill_color = rgb(40,40,40);
button_thick->fore_color = rgb(255,255,255);
#endif
button_thin->button_state = TTK_BUTTON_STATE_DOWN;
button_thick->button_state = TTK_BUTTON_STATE_NORMAL;
thick = 0;
ttk_render();
}
static void resize_finish(int width, int height) {
yutani_window_resize_accept(yctx, wina, width, height);
reinit_graphics_yutani(ctx, wina);
((ttk_object *)close_button)->x = wina->width - 28;
ttk_render();
yutani_window_resize_done(yctx, wina);
yutani_flip(yctx, wina);
}
static void resize_button(void * button, struct yutani_msg_window_mouse_event * event) {
yutani_window_resize(yctx, wina, 600, 600);
yutani_msg_t * m = yutani_wait_for(yctx, YUTANI_MSG_RESIZE_OFFER);
struct yutani_msg_window_resize * wr = (void*)m->data;
resize_finish(wr->width, wr->height);
free(m);
}
void keep_drawing(struct yutani_msg_window_mouse_event * mouse) {
double thickness = thick ? 2.0 : 0.5;;
int old_x = mouse->old_x - ((ttk_object *)drawing_surface)->x;
int old_y = mouse->old_y - ((ttk_object *)drawing_surface)->y;
int new_x = mouse->new_x - ((ttk_object *)drawing_surface)->x;
int new_y = mouse->new_y - ((ttk_object *)drawing_surface)->y;
{
cairo_t * cr = cairo_create(internal_surface);
cairo_set_source_rgb(cr, _RED(drawing_color) / 255.0, _GRE(drawing_color) / 255.0, _BLU(drawing_color) / 255.0);
cairo_set_line_width(cr, thickness);
cairo_move_to(cr, old_x, old_y);
cairo_line_to(cr, new_x, new_y);
cairo_stroke(cr);
cairo_destroy(cr);
}
{
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, wina->width);
cairo_surface_t * core_surface = cairo_image_surface_create_for_data(ctx->backbuffer, CAIRO_FORMAT_ARGB32, wina->width, wina->height, stride);
cairo_t * cr = cairo_create(core_surface);
cairo_rectangle(cr, ((ttk_object*)drawing_surface)->x, ((ttk_object*)drawing_surface)->y, ((ttk_object*)drawing_surface)->width, ((ttk_object*)drawing_surface)->height);
cairo_clip(cr);
cairo_set_source_rgb(cr, _RED(drawing_color) / 255.0, _GRE(drawing_color) / 255.0, _BLU(drawing_color) / 255.0);
cairo_set_line_width(cr, thickness);
cairo_move_to(cr, old_x + ((ttk_object*)drawing_surface)->x, old_y + ((ttk_object*)drawing_surface)->y);
cairo_line_to(cr, new_x + ((ttk_object*)drawing_surface)->x, new_y + ((ttk_object*)drawing_surface)->y);
cairo_stroke(cr);
cairo_destroy(cr);
cairo_surface_destroy(core_surface);
flip(ctx);
}
}
int main (int argc, char ** argv) {
int left = 30;
int top = 30;
int width = 450;
int height = 450;
yctx = yutani_init();
/* Do something with a window */
wina = yutani_window_create(yctx, width, height);
ctx = init_graphics_yutani_double_buffer(wina);
draw_fill(ctx, rgb(255,255,255));
init_decorations();
yutani_window_advertise(yctx, wina, "Draw!");
setup_ttk(wina);
close_button = ttk_decor_button_close(quit_app);
((ttk_object *)close_button)->x = width - 28;
((ttk_object *)close_button)->y = 16;
button_blue = ttk_button_new("Blue", set_color);
ttk_position((ttk_object *)button_blue, decor_left_width + 3, decor_top_height + 3, 100, 20);
button_blue->fill_color = rgb(0,0,255);
button_blue->fore_color = rgb(255,255,255);
button_green = ttk_button_new("Green", set_color);
ttk_position((ttk_object *)button_green, decor_left_width + 106, decor_top_height + 3, 100, 20);
button_green->fill_color = rgb(0,255,0);
button_green->fore_color = rgb(0,0,0);
button_red = ttk_button_new("Red", set_color);
ttk_position((ttk_object *)button_red, decor_left_width + 209, decor_top_height + 3, 100, 20);
button_red->fill_color = rgb(255,0,0);
button_red->fore_color = rgb(255,255,255);
button_thick = ttk_button_new("Thick", set_thickness_thick);
ttk_position((ttk_object *)button_thick, decor_left_width + 312, decor_top_height + 3, 50, 20);
button_thick->fill_color = rgb(40,40,40);
button_thick->fore_color = rgb(255,255,255);
button_thin = ttk_button_new("Thin", set_thickness_thin);
ttk_position((ttk_object *)button_thin, decor_left_width + 362, decor_top_height + 3, 50, 20);
button_thin->fill_color = rgb(127,127,127);
button_thin->fore_color = rgb(255,255,255);
ttk_button * button_resize = ttk_button_new("*", resize_button);
ttk_position((ttk_object *)button_resize, decor_left_width + 410, decor_top_height + 3, 20, 20);
button_resize->fill_color = rgb(127,127,127);
button_resize->fore_color = rgb(255,255,255);
drawing_surface = ttk_raw_surface_new(width - 30, height - 70);
((ttk_object *)drawing_surface)->y = 60;
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, drawing_surface->surface->width);
internal_surface = cairo_image_surface_create_for_data(drawing_surface->surface->backbuffer, CAIRO_FORMAT_ARGB32, drawing_surface->surface->width, drawing_surface->surface->height, stride);
drawing_color = rgb(255,0,0);
ttk_render();
while (!quit) {
yutani_msg_t * m = yutani_poll(yctx);
if (m) {
switch (m->type) {
case YUTANI_MSG_KEY_EVENT:
{
struct yutani_msg_key_event * ke = (void*)m->data;
if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {
quit = 1;
break;
}
}
break;
case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
{
struct yutani_msg_window_focus_change * wf = (void*)m->data;
yutani_window_t * win = hashmap_get(yctx->windows, (void*)wf->wid);
if (win) {
win->focused = wf->focused;
ttk_render();
}
}
break;
case YUTANI_MSG_WINDOW_MOUSE_EVENT:
{
struct yutani_msg_window_mouse_event * me = (void*)m->data;
if (me->command == YUTANI_MOUSE_EVENT_DRAG && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
keep_drawing(me);
yutani_flip(yctx, wina);
} else if (me->command == YUTANI_MOUSE_EVENT_RAISE) {
ttk_render();
} else {
ttk_check_click(me);
}
}
break;
case YUTANI_MSG_RESIZE_OFFER:
{
struct yutani_msg_window_resize * wr = (void*)m->data;
resize_finish(wr->width, wr->height);
}
break;
default:
break;
}
free(m);
}
}
yutani_close(yctx, wina);
return 0;
}