diff --git a/userspace/gui/basic/draw-beta.c b/userspace/gui/basic/draw-beta.c new file mode 100644 index 00000000..237f4f19 --- /dev/null +++ b/userspace/gui/basic/draw-beta.c @@ -0,0 +1,467 @@ + +/* + * draw + * + * Windowed graphical drawing tool. + * Simple painting application. + * + * This is also the playground for the work-in-progress + * ToaruToolKit GUI toolkit. + */ +#include + +#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 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 + +/* + * 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; + + 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(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 * 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(); +} + +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; + + { + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, drawing_surface->surface->width); + cairo_surface_t * internal_surface = cairo_image_surface_create_for_data(drawing_surface->surface->backbuffer, CAIRO_FORMAT_ARGB32, drawing_surface->surface->width, drawing_surface->surface->height, stride); + 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); + cairo_surface_destroy(internal_surface); + } + +} + +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(); + + window_t _wina; + _wina.buffer = wina->buffer; + _wina.width = wina->width; + _wina.height = wina->height; + _wina.focused = wina->focused; + + setup_ttk(&_wina); + + ttk_button * 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); + +#if 0 + ttk_button * button_quit = ttk_button_new("X", quit_app); + ttk_position((ttk_object *)button_quit, width - 33, 12, 20, 20); + button_quit->fill_color = rgb(255,0,0); + button_quit->fore_color = rgb(255,255,255); +#endif + + drawing_surface = ttk_raw_surface_new(width - 30, height - 70); + ((ttk_object *)drawing_surface)->y = 60; + + 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); + ttk_render(); + } else { + ttk_check_click(me); + } + } + break; + default: + break; + } + free(m); + } + } + + yutani_close(yctx, wina); + + return 0; +}