From 65109cc3c40758612b7a4de590ccf828942b4121 Mon Sep 17 00:00:00 2001 From: Daanish Date: Wed, 6 May 2026 22:01:48 +0100 Subject: [PATCH] Add ruby bindings for internal structures, allow better controlled localization --- include/app/app.h | 45 +- include/binding/definitions.h | 937 ++++++++++++++++++++++++++-- include/binding/ruby.h | 2 + include/localization/localization.h | 92 ++- include/utils.h | 6 +- include/window/window.h | 13 +- samples/button.rb | 39 +- src/window/window.cc | 27 +- 8 files changed, 1047 insertions(+), 114 deletions(-) diff --git a/include/app/app.h b/include/app/app.h index 5692f66..66aafeb 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -16,6 +16,7 @@ struct Element { Vec2 position = {0, 0}; float rotation = 0; Vec2 scale = {1, 1}; + float alpha = 1.0f; float z = 0; bool click_through = false; Ruby::Block on_click; @@ -56,28 +57,38 @@ struct EImage : Element { return; int frame_index = (int)((abs_time * animation.frame_rate) / 1000) % animation.frames.size(); - window.draw(animation.frames[frame_index], position, z, rotation, scale); + window.draw(animation.frames[frame_index], position, z, rotation, scale, alpha); } }; struct EText : Element { Localization::Key key; uint64_t font_id = UINT64_MAX; + Color color = {255, 255, 255}; + mrb_value params = mrb_nil_value(); ~EText() { if (text_id != UINT64_MAX) text_pool.release(text_id); if (font_id != UINT64_MAX) font_pool.release(font_id); + if (!mrb_nil_p(params)) + mrb_gc_unregister(Ruby::mrb, params); } - Color color = {255, 255, 255, 255}; - - EText(Localization::Key key, uint64_t font_id, Color color) - : Element(Type::TEXT), key(key), font_id(font_id), color(color) { + EText(Localization::Key key, uint64_t font_id, Color color, mrb_value params) + : Element(Type::TEXT), key(key), font_id(font_id), color(color), params(params) { + if (!mrb_nil_p(params)) + mrb_gc_protect(Ruby::mrb, params); font_pool.use(font_id); } + void reload_params() { + // Force re-render by resetting last_key (without affecting this frame) + // this can be changed to use a dirty flag or version counter if needed + last_key = UINT16_MAX; + } + Vec2 size(Window *window) override { if (text_id == UINT64_MAX) return {0, 0}; @@ -85,26 +96,32 @@ struct EText : Element { }; void render(Window &window, uint64_t) override { - if (last_language != Localization::current_language || last_key != key || text_id == UINT64_MAX) { - std::string localized_str = Localization::get(key); + if ( + last_language != Localization::current_language + || last_key != key || text_id == UINT64_MAX + || last_color != color + ) { + std::string localized_str = Localization::get(Ruby::mrb, key, params); if (text_id != UINT64_MAX) text_pool.release(text_id); text_id = window.create_text(font_id, localized_str.c_str(), localized_str.size(), color); last_language = Localization::current_language; last_key = key; + last_color = color; } - window.write(text_id, position, z, rotation, scale); + window.write(text_id, position, z, rotation, scale, alpha); } private: uint64_t text_id = UINT64_MAX; - Localization::LanguageCode last_language = UINT_MAX; - Localization::Key last_key = UINT_MAX; + Color last_color = color; + Localization::LanguageCode last_language = UINT32_MAX; + Localization::Key last_key = UINT16_MAX; }; struct ERect : Element { Vec2 shape = {0, 0}; - Color color = {255, 255, 255, 255}; + Color color = {255, 255, 255}; Vec2 size(Window *) override { return shape; }; @@ -112,7 +129,7 @@ struct ERect : Element { ERect(Vec2 shape, Color color) : Element(Type::RECT), shape(shape), color(color) {} void render(Window &window, uint64_t) override { - window.draw_rect(Rect::from_size(position, shape), color, z, rotation); + window.draw_rect(Rect::from_size(position, shape), color, z, rotation, scale, alpha); } }; @@ -195,6 +212,10 @@ struct App { bool running = true; Vec2 mouse_position = {-1, -1}; + void exit() { + running = false; + } + void switch_to_scene(uint64_t scene_id) { scene_pool.release(current_scene_id); scene_pool.use(scene_id); diff --git a/include/binding/definitions.h b/include/binding/definitions.h index 4504cf1..9e131b2 100644 --- a/include/binding/definitions.h +++ b/include/binding/definitions.h @@ -3,8 +3,6 @@ #include "app/app.h" -#warning "Define ==, eql?, hash for each class" - namespace definitions { inline mrb_value wrap(mrb_state *mrb, RClass *cls, const mrb_data_type *type, uint64_t id) { RBasic *obj_b = mrb_obj_alloc(mrb, MRB_TT_DATA, cls); @@ -63,6 +61,24 @@ inline uint64_t get_id_Font(mrb_state *mrb, mrb_value self) { return wrapper->id; } +static mrb_value font_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Font"))) + return mrb_false_value(); + + uint64_t id_self = get_id_Font(mrb, self); + uint64_t id_other = get_id_Font(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value font_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Font(mrb, self); + return mrb_fixnum_value(id); +} + static void Image_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -84,14 +100,11 @@ static mrb_value Image_init(mrb_state *mrb, mrb_value self) { mrb_value kwargs; mrb_get_args(mrb, "z|H", &path, &kwargs); - float alpha = 1.0f; bool pixel_art = false; - if (!mrb_nil_p(kwargs)) { - HASH_FLOAT(kwargs, alpha, alpha); + if (!mrb_nil_p(kwargs)) HASH_BOOL(kwargs, pixel_art, pixel_art); - } - s->id = window.load_image(path, alpha, pixel_art); + s->id = window.load_image(path, pixel_art); mrb_data_init(self, s, &Image_type); return self; @@ -103,6 +116,24 @@ inline uint64_t get_id_Image(mrb_state *mrb, mrb_value self) { return wrapper->id; } +static mrb_value image_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Image"))) + return mrb_false_value(); + + uint64_t id_self = get_id_Image(mrb, self); + uint64_t id_other = get_id_Image(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value image_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Image(mrb, self); + return mrb_fixnum_value(id); +} + static void Animation_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -147,7 +178,7 @@ static mrb_value Animation_init(mrb_state *mrb, mrb_value self) { frame_ids.push_back(frame_id); } - Animation *animation = new Animation{frame_ids, fps, true}; + Animation *animation = new Animation{frame_ids, fps}; s->id = animation_pool.acquire(animation); animation_pool.use(s->id); @@ -161,6 +192,88 @@ inline uint64_t get_id_Animation(mrb_state *mrb, mrb_value self) { return wrapper->id; } +static mrb_value animation_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Animation"))) + return mrb_false_value(); + + uint64_t id_self = get_id_Animation(mrb, self); + uint64_t id_other = get_id_Animation(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value animation_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Animation(mrb, self); + return mrb_fixnum_value(id); +} + +static mrb_value animation_frames_set(mrb_state *mrb, mrb_value self) { + const mrb_value *frames_val; + mrb_int frames_len; + mrb_get_args(mrb, "a", &frames_val, &frames_len); + + uint64_t id = get_id_Animation(mrb, self); + Animation *animation = animation_pool[id]; + + std::vector new_frame_ids; + for (mrb_int i = 0; i < frames_len; i++) { + mrb_value frame_val = frames_val[i]; + if (!mrb_obj_is_kind_of(mrb, frame_val, mrb_class_get(mrb, "Image"))) { + mrb_raise( + mrb, + mrb_class_get(mrb, "Exception"), + "Expected an array of Image objects" + ); + return mrb_nil_value(); + } + uint64_t frame_id = get_id_Image(mrb, frame_val); + image_pool.use(frame_id); + new_frame_ids.push_back(frame_id); + } + + // Release old frames + for (uint64_t frame_id : animation->frames) + image_pool.release(frame_id); + + animation->frames = std::move(new_frame_ids); + + return mrb_nil_value(); +} + +static mrb_value animation_frames_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Animation(mrb, self); + Animation *animation = animation_pool[id]; + + mrb_value frames_array = mrb_ary_new_capa(mrb, animation->frames.size()); + for (uint64_t frame_id : animation->frames) { + image_pool.use(frame_id); + mrb_value frame_obj = wrap(mrb, mrb_class_get(mrb, "Image"), &Image_type, frame_id); + mrb_ary_push(mrb, frames_array, frame_obj); + } + + return frames_array; +} + +static mrb_value animation_fps_set(mrb_state *mrb, mrb_value self) { + mrb_float fps; + mrb_get_args(mrb, "f", &fps); + + uint64_t id = get_id_Animation(mrb, self); + Animation *animation = animation_pool[id]; + animation->frame_rate = fps; + + return mrb_nil_value(); +} + +static mrb_value animation_fps_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Animation(mrb, self); + Animation *animation = animation_pool[id]; + return mrb_float_value(mrb, animation->frame_rate); +} + static void ElementText_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -180,10 +293,12 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) { mrb_data_init(self, nullptr, &ElementText_type); Wrapper *s = new Wrapper(); - mrb_value font_val; mrb_sym key; + mrb_value font_val; + mrb_int color_i = 0xFFFFFF; + mrb_value t_args; mrb_value kwargs; - mrb_get_args(mrb, "no|H", &key, &font_val, &kwargs); + mrb_get_args(mrb, "noi|HH", &key, &font_val, &color_i, &t_args, &kwargs); auto key_id = Localization::key_from_sym(mrb, key); @@ -192,26 +307,41 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) { return mrb_nil_value(); } - float x = 0; - float y = 0; - float z = 0; + float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1; bool click_through = false; if (!mrb_nil_p(kwargs)) { mrb_value pos_hash = HASH_GET(kwargs, position); - HASH_FLOAT(pos_hash, x, x); - HASH_FLOAT(pos_hash, y, y); + if (!mrb_nil_p(pos_hash)) { + HASH_FLOAT(pos_hash, x, x); + HASH_FLOAT(pos_hash, y, y); + } + + mrb_value scale_hash = HASH_GET(kwargs, scale); + if (!mrb_nil_p(scale_hash)) { + HASH_FLOAT(scale_hash, x, scale_x); + HASH_FLOAT(scale_hash, y, scale_y); + } HASH_FLOAT(kwargs, z, z); + HASH_FLOAT(kwargs, rotation, rotation); + HASH_FLOAT(kwargs, alpha, alpha); HASH_BOOL(kwargs, click_through, click_through); } - Vec2 position = {x, y}; - uint64_t id = get_id_Font(mrb, font_val); font_pool.use(id); - app::EText *element = new app::EText(key_id, id, {255, 255, 255, 255}); - element->position = position; + Color color = { + (uint8_t)((color_i >> 16) & 0xFF), + (uint8_t)((color_i >> 8) & 0xFF), + (uint8_t)((color_i) & 0xFF) + }; + + app::EText *element = new app::EText(key_id, id, color, t_args); + element->position = {x, y}; + element->scale = {scale_x, scale_y}; + element->rotation = rotation; + element->alpha = alpha; element->z = z; element->click_through = click_through; @@ -237,6 +367,217 @@ static mrb_value element_text_on_click(mrb_state *mrb, mrb_value self) { return self; } +static mrb_value element_text_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "ElementText"))) + return mrb_false_value(); + + uint64_t id_self = get_id_ElementText(mrb, self); + uint64_t id_other = get_id_ElementText(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value element_text_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + return mrb_fixnum_value(id); +} + +static mrb_value element_text_key_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_symbol_value(Localization::lang_to_sym(mrb, element->key)); +} + +static mrb_value element_text_key_set(mrb_state *mrb, mrb_value self) { + mrb_sym key_sym; + mrb_get_args(mrb, "n", &key_sym); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->key = Localization::lang_from_sym(mrb, key_sym); + + return mrb_nil_value(); +} + +static mrb_value element_text_font_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + if (element->font_id == UINT64_MAX) + return mrb_nil_value(); + font_pool.use(element->font_id); + return wrap(mrb, mrb_class_get(mrb, "Font"), &Font_type, element->font_id); +} + +static mrb_value element_text_font_set(mrb_state *mrb, mrb_value self) { + mrb_value font_val; + mrb_get_args(mrb, "o", &font_val); + + if (!mrb_obj_is_kind_of(mrb, font_val, mrb_class_get(mrb, "Font"))) { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected a Font object"); + return mrb_nil_value(); + } + + uint64_t font_id = get_id_Font(mrb, font_val); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + if (element->font_id != UINT64_MAX) + font_pool.release(element->font_id); + element->font_id = font_id; + font_pool.use(font_id); + + return mrb_nil_value(); +} + +static mrb_value element_text_color_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_fixnum_value( + (element->color.r << 16) | (element->color.g << 8) | (element->color.b) + ); +} + +static mrb_value element_text_color_set(mrb_state *mrb, mrb_value self) { + mrb_int color_val; + mrb_get_args(mrb, "i", &color_val); + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->color = { + (uint8_t)((color_val >> 16) & 0xFF), + (uint8_t)((color_val >> 8) & 0xFF), + (uint8_t)((color_val) & 0xFF) + }; + return mrb_nil_value(); +} + +static mrb_value element_text_params(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return element->params; +} + +static mrb_value element_text_params_load(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->reload_params(); + return mrb_nil_value(); +} + +static mrb_value element_text_position_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + mrb_value pos_hash = mrb_hash_new(mrb); + HASH_SET(pos_hash, x, mrb_float_value(mrb, element->position.x)); + HASH_SET(pos_hash, y, mrb_float_value(mrb, element->position.y)); + return pos_hash; +} + +static mrb_value element_text_position_set(mrb_state *mrb, mrb_value self) { + mrb_value pos_hash; + mrb_get_args(mrb, "H", &pos_hash); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + + HASH_FLOAT(pos_hash, x, element->position.x); + HASH_FLOAT(pos_hash, y, element->position.y); + + return mrb_nil_value(); +} + +static mrb_value element_text_rotation_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_float_value(mrb, element->rotation); +} + +static mrb_value element_text_rotation_set(mrb_state *mrb, mrb_value self) { + mrb_float rotation; + mrb_get_args(mrb, "f", &rotation); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->rotation = rotation; + + return mrb_nil_value(); +} + +static mrb_value element_text_scale_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + mrb_value scale_hash = mrb_hash_new(mrb); + HASH_SET(scale_hash, x, mrb_float_value(mrb, element->scale.x)); + HASH_SET(scale_hash, y, mrb_float_value(mrb, element->scale.y)); + return scale_hash; +} + +static mrb_value element_text_scale_set(mrb_state *mrb, mrb_value self) { + mrb_value scale_hash; + mrb_get_args(mrb, "H", &scale_hash); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + + HASH_FLOAT(scale_hash, x, element->scale.x); + HASH_FLOAT(scale_hash, y, element->scale.y); + + return mrb_nil_value(); +} + +static mrb_value element_text_alpha_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_float_value(mrb, element->alpha); +} + +static mrb_value element_text_alpha_set(mrb_state *mrb, mrb_value self) { + mrb_float alpha; + mrb_get_args(mrb, "f", &alpha); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->alpha = alpha; + + return mrb_nil_value(); +} + +static mrb_value element_text_z_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_float_value(mrb, element->z); +} + +static mrb_value element_text_z_set(mrb_state *mrb, mrb_value self) { + mrb_float z; + mrb_get_args(mrb, "f", &z); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->z = z; + + return mrb_nil_value(); +} + +static mrb_value element_text_click_through_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + return mrb_bool_value(element->click_through); +} + +static mrb_value element_text_click_through_set(mrb_state *mrb, mrb_value self) { + mrb_bool click_through; + mrb_get_args(mrb, "b", &click_through); + + uint64_t id = get_id_ElementText(mrb, self); + app::EText *element = (app::EText *)app::element_pool[id]; + element->click_through = click_through; + + return mrb_nil_value(); +} + static void ElementRect_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -245,7 +586,8 @@ static void ElementRect_free(mrb_state *mrb, void *ptr) { } static const struct mrb_data_type ElementRect_type = { - "ElementRect", ElementRect_free + "ElementRect", + ElementRect_free }; static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) { @@ -256,37 +598,48 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) { mrb_data_init(self, nullptr, &ElementRect_type); Wrapper *s = new Wrapper(); - mrb_value shape_val; - mrb_int color; + mrb_int w, h; + mrb_int color_i = 0xFFFFFF; mrb_value kwargs; - mrb_get_args(mrb, "Hi|H", &shape_val, &color, &kwargs); + mrb_get_args(mrb, "iii|H", &w, &h, &color_i, &kwargs); - float x = 0, y = 0, w = 0, h = 0; - if (!mrb_nil_p(shape_val)) { - HASH_FLOAT(shape_val, x, x); - HASH_FLOAT(shape_val, y, y); - HASH_FLOAT(shape_val, w, w); - HASH_FLOAT(shape_val, h, h); - } - - float z = 0; + float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1; bool click_through = false; if (!mrb_nil_p(kwargs)) { + mrb_value pos_hash = HASH_GET(kwargs, position); + if (!mrb_nil_p(pos_hash)) { + HASH_FLOAT(pos_hash, x, x); + HASH_FLOAT(pos_hash, y, y); + } + + mrb_value scale_hash = HASH_GET(kwargs, scale); + if (!mrb_nil_p(scale_hash)) { + HASH_FLOAT(scale_hash, x, scale_x); + HASH_FLOAT(scale_hash, y, scale_y); + } + HASH_FLOAT(kwargs, z, z); + HASH_FLOAT(kwargs, rotation, rotation); + HASH_FLOAT(kwargs, alpha, alpha); HASH_BOOL(kwargs, click_through, click_through); } - Vec2 position = {x, y}; - Vec2 shape = {w, h}; + Vec2 shape = {(float)w, (float)h}; + + Color color = { + (uint8_t)((color_i >> 16) & 0xFF), + (uint8_t)((color_i >> 8) & 0xFF), + (uint8_t)((color_i) & 0xFF) + }; app::ERect *element = new app::ERect( shape, - {(uint8_t)((color >> 24) & 0xFF), - (uint8_t)((color >> 16) & 0xFF), - (uint8_t)((color >> 8) & 0xFF), - (uint8_t)(color & 0xFF)} + color ); - element->position = position; + element->position = {x, y}; + element->scale = {scale_x, scale_y}; + element->rotation = rotation; + element->alpha = alpha; element->z = z; element->click_through = click_through; @@ -312,16 +665,49 @@ static mrb_value element_rect_on_click(mrb_state *mrb, mrb_value self) { return self; } +static mrb_value element_rect_width_set(mrb_state *mrb, mrb_value self) { + mrb_float width; + mrb_get_args(mrb, "f", &width); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->shape.x = width; + + return mrb_nil_value(); +} + +static mrb_value element_rect_width_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_float_value(mrb, element->shape.x); +} + +static mrb_value element_rect_height_set(mrb_state *mrb, mrb_value self) { + mrb_float height; + mrb_get_args(mrb, "f", &height); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->shape.y = height; + + return mrb_nil_value(); +} + +static mrb_value element_rect_height_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_float_value(mrb, element->shape.y); +} + static mrb_value element_rect_color_set(mrb_state *mrb, mrb_value self) { mrb_value color_val; mrb_get_args(mrb, "o", &color_val); uint64_t id = get_id_ElementRect(mrb, self); app::ERect *element = (app::ERect *)app::element_pool[id]; element->color = { - (uint8_t)((mrb_fixnum(color_val) >> 24) & 0xFF), (uint8_t)((mrb_fixnum(color_val) >> 16) & 0xFF), (uint8_t)((mrb_fixnum(color_val) >> 8) & 0xFF), - (uint8_t)(mrb_fixnum(color_val) & 0xFF) + (uint8_t)((mrb_fixnum(color_val)) & 0xFF) }; return self; } @@ -330,11 +716,141 @@ static mrb_value element_rect_color_get(mrb_state *mrb, mrb_value self) { uint64_t id = get_id_ElementRect(mrb, self); app::ERect *element = (app::ERect *)app::element_pool[id]; uint32_t color = - ((uint32_t)element->color.r << 24) | ((uint32_t)element->color.g << 16) - | ((uint32_t)element->color.b << 8) | (uint32_t)element->color.a; + ((uint32_t)element->color.r << 16) | ((uint32_t)element->color.g << 8) + | ((uint32_t)element->color.b); return mrb_fixnum_value(color); } +static mrb_value element_rect_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "ElementRect"))) + return mrb_false_value(); + + uint64_t id_self = get_id_ElementRect(mrb, self); + uint64_t id_other = get_id_ElementRect(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value element_rect_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + return mrb_fixnum_value(id); +} + +static mrb_value element_rect_position_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + mrb_value pos_hash = mrb_hash_new(mrb); + HASH_SET(pos_hash, x, mrb_float_value(mrb, element->position.x)); + HASH_SET(pos_hash, y, mrb_float_value(mrb, element->position.y)); + return pos_hash; +} + +static mrb_value element_rect_position_set(mrb_state *mrb, mrb_value self) { + mrb_value pos_hash; + mrb_get_args(mrb, "H", &pos_hash); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + + HASH_FLOAT(pos_hash, x, element->position.x); + HASH_FLOAT(pos_hash, y, element->position.y); + + return mrb_nil_value(); +} + +static mrb_value element_rect_rotation_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_float_value(mrb, element->rotation); +} + +static mrb_value element_rect_rotation_set(mrb_state *mrb, mrb_value self) { + mrb_float rotation; + mrb_get_args(mrb, "f", &rotation); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->rotation = rotation; + + return mrb_nil_value(); +} + +static mrb_value element_rect_scale_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + mrb_value scale_hash = mrb_hash_new(mrb); + HASH_SET(scale_hash, x, mrb_float_value(mrb, element->scale.x)); + HASH_SET(scale_hash, y, mrb_float_value(mrb, element->scale.y)); + return scale_hash; +} + +static mrb_value element_rect_scale_set(mrb_state *mrb, mrb_value self) { + mrb_value scale_hash; + mrb_get_args(mrb, "H", &scale_hash); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + + HASH_FLOAT(scale_hash, x, element->scale.x); + HASH_FLOAT(scale_hash, y, element->scale.y); + + return mrb_nil_value(); +} + +static mrb_value element_rect_alpha_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_float_value(mrb, element->alpha); +} + +static mrb_value element_rect_alpha_set(mrb_state *mrb, mrb_value self) { + mrb_float alpha; + mrb_get_args(mrb, "f", &alpha); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->alpha = alpha; + + return mrb_nil_value(); +} + +static mrb_value element_rect_z_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_float_value(mrb, element->z); +} + +static mrb_value element_rect_z_set(mrb_state *mrb, mrb_value self) { + mrb_float z; + mrb_get_args(mrb, "f", &z); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->z = z; + + return mrb_nil_value(); +} + +static mrb_value element_rect_click_through_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + return mrb_bool_value(element->click_through); +} + +static mrb_value element_rect_click_through_set(mrb_state *mrb, mrb_value self) { + mrb_bool click_through; + mrb_get_args(mrb, "b", &click_through); + + uint64_t id = get_id_ElementRect(mrb, self); + app::ERect *element = (app::ERect *)app::element_pool[id]; + element->click_through = click_through; + + return mrb_nil_value(); +} + static void ElementImage_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -343,7 +859,8 @@ static void ElementImage_free(mrb_state *mrb, void *ptr) { } static const struct mrb_data_type ElementImage_type = { - "ElementImage", ElementImage_free + "ElementImage", + ElementImage_free }; static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) { @@ -370,16 +887,24 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) { return mrb_nil_value(); } - float x = 0; - float y = 0; - float z = 0; + float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1; bool click_through = false; if (!mrb_nil_p(kwargs)) { mrb_value pos_hash = HASH_GET(kwargs, position); - HASH_FLOAT(pos_hash, x, x); - HASH_FLOAT(pos_hash, y, y); + if (!mrb_nil_p(pos_hash)) { + HASH_FLOAT(pos_hash, x, x); + HASH_FLOAT(pos_hash, y, y); + } + + mrb_value scale_hash = HASH_GET(kwargs, scale); + if (!mrb_nil_p(scale_hash)) { + HASH_FLOAT(scale_hash, x, scale_x); + HASH_FLOAT(scale_hash, y, scale_y); + } HASH_FLOAT(kwargs, z, z); + HASH_FLOAT(kwargs, rotation, rotation); + HASH_FLOAT(kwargs, alpha, alpha); HASH_BOOL(kwargs, click_through, click_through); } @@ -387,6 +912,9 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) { app::EImage *element = new app::EImage(id); element->position = {x, y}; + element->scale = {scale_x, scale_y}; + element->rotation = rotation; + element->alpha = alpha; element->z = z; element->click_through = click_through; @@ -412,6 +940,175 @@ static mrb_value element_image_on_click(mrb_state *mrb, mrb_value self) { return self; } +static mrb_value element_image_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "ElementImage"))) + return mrb_false_value(); + + uint64_t id_self = get_id_ElementImage(mrb, self); + uint64_t id_other = get_id_ElementImage(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value element_image_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + return mrb_fixnum_value(id); +} + +static mrb_value element_image_animation_set(mrb_state *mrb, mrb_value self) { + mrb_value animation; + mrb_get_args(mrb, "o", &animation); + + if (!mrb_obj_is_kind_of( + mrb, + animation, + mrb_class_get(mrb, "Animation") + )) { + mrb_raise( + mrb, + mrb_class_get(mrb, "Exception"), + "Expected an Animation object" + ); + return mrb_nil_value(); + } + + uint64_t animation_id = get_id_Animation(mrb, animation); + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + + animation_pool.use(animation_id); + animation_pool.release(element->animation_id); + + element->animation_id = animation_id; + + return self; +} + +static mrb_value element_image_animation_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + uint64_t animation_id = element->animation_id; + if (animation_id == UINT64_MAX) + return mrb_nil_value(); + animation_pool.use(animation_id); + return wrap(mrb, mrb_class_get(mrb, "Animation"), &Animation_type, animation_id); +} + +static mrb_value element_image_position_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + mrb_value pos_hash = mrb_hash_new(mrb); + HASH_SET(pos_hash, x, mrb_float_value(mrb, element->position.x)); + HASH_SET(pos_hash, y, mrb_float_value(mrb, element->position.y)); + return pos_hash; +} + +static mrb_value element_image_position_set(mrb_state *mrb, mrb_value self) { + mrb_value pos_hash; + mrb_get_args(mrb, "H", &pos_hash); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + + HASH_FLOAT(pos_hash, x, element->position.x); + HASH_FLOAT(pos_hash, y, element->position.y); + + return mrb_nil_value(); +} + +static mrb_value element_image_rotation_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + return mrb_float_value(mrb, element->rotation); +} + +static mrb_value element_image_rotation_set(mrb_state *mrb, mrb_value self) { + mrb_float rotation; + mrb_get_args(mrb, "f", &rotation); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + element->rotation = rotation; + + return mrb_nil_value(); +} + +static mrb_value element_image_scale_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + mrb_value scale_hash = mrb_hash_new(mrb); + HASH_SET(scale_hash, x, mrb_float_value(mrb, element->scale.x)); + HASH_SET(scale_hash, y, mrb_float_value(mrb, element->scale.y)); + return scale_hash; +} + +static mrb_value element_image_scale_set(mrb_state *mrb, mrb_value self) { + mrb_value scale_hash; + mrb_get_args(mrb, "H", &scale_hash); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + + HASH_FLOAT(scale_hash, x, element->scale.x); + HASH_FLOAT(scale_hash, y, element->scale.y); + + return mrb_nil_value(); +} + +static mrb_value element_image_alpha_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + return mrb_float_value(mrb, element->alpha); +} + +static mrb_value element_image_alpha_set(mrb_state *mrb, mrb_value self) { + mrb_float alpha; + mrb_get_args(mrb, "f", &alpha); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + element->alpha = alpha; + + return mrb_nil_value(); +} + +static mrb_value element_image_z_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + return mrb_float_value(mrb, element->z); +} + +static mrb_value element_image_z_set(mrb_state *mrb, mrb_value self) { + mrb_float z; + mrb_get_args(mrb, "f", &z); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + element->z = z; + + return mrb_nil_value(); +} + +static mrb_value element_image_click_through_get(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + return mrb_bool_value(element->click_through); +} + +static mrb_value element_image_click_through_set(mrb_state *mrb, mrb_value self) { + mrb_bool click_through; + mrb_get_args(mrb, "b", &click_through); + + uint64_t id = get_id_ElementImage(mrb, self); + app::EImage *element = (app::EImage *)app::element_pool[id]; + element->click_through = click_through; + + return mrb_nil_value(); +} + static void Scene_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -561,6 +1258,7 @@ static mrb_value scene_elements_at(mrb_state *mrb, mrb_value self) { mrb_value result = mrb_ary_new_capa(mrb, element_ids.size()); for (uint64_t element_id : element_ids) { app::Element *element = app::element_pool[element_id]; + app::element_pool.use(element_id); if (element->type == Type::TEXT) { RClass *ElementText_class = mrb_class_get(mrb, "ElementText"); mrb_value obj = wrap(mrb, ElementText_class, &ElementText_type, element_id); @@ -581,6 +1279,54 @@ static mrb_value scene_elements_at(mrb_state *mrb, mrb_value self) { return result; } +static mrb_value scene_elements(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Scene(mrb, self); + app::Scene *scene = app::scene_pool[id]; + + std::vector element_ids = scene->element_ids; + + mrb_value result = mrb_ary_new_capa(mrb, element_ids.size()); + for (uint64_t element_id : element_ids) { + app::Element *element = app::element_pool[element_id]; + app::element_pool.use(element_id); + if (element->type == Type::TEXT) { + RClass *ElementText_class = mrb_class_get(mrb, "ElementText"); + mrb_value obj = wrap(mrb, ElementText_class, &ElementText_type, element_id); + mrb_ary_push(mrb, result, obj); + } else if (element->type == Type::RECT) { + RClass *ElementRect_class = mrb_class_get(mrb, "ElementRect"); + mrb_value obj = wrap(mrb, ElementRect_class, &ElementRect_type, element_id); + mrb_ary_push(mrb, result, obj); + } else if (element->type == Type::IMAGE) { + RClass *ElementImage_class = mrb_class_get(mrb, "ElementImage"); + mrb_value obj = wrap(mrb, ElementImage_class, &ElementImage_type, element_id); + mrb_ary_push(mrb, result, obj); + } else { + continue; + } + } + + return result; +} + +static mrb_value scene_equal(mrb_state *mrb, mrb_value self) { + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Scene"))) + return mrb_false_value(); + + uint64_t id_self = get_id_Scene(mrb, self); + uint64_t id_other = get_id_Scene(mrb, other); + + return mrb_bool_value(id_self == id_other); +} + +static mrb_value scene_hash(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_Scene(mrb, self); + return mrb_fixnum_value(id); +} + static mrb_value app_run(mrb_state *mrb, mrb_value) { mrb_int width, height; char *title = nullptr; @@ -643,7 +1389,7 @@ static mrb_value app_localize(mrb_state *mrb, mrb_value) { key_id = it->second; } - Localization::localized_strings[Localization::join_keys(lang, key_id)] = text; + Localization::set(lang, key_id, text); return mrb_nil_value(); } @@ -655,6 +1401,10 @@ static mrb_value app_language_set(mrb_state *mrb, mrb_value) { return mrb_nil_value(); } +static mrb_value app_language_get(mrb_state *mrb, mrb_value) { + return mrb_symbol_value(Localization::lang_to_sym(mrb, Localization::current_language)); +} + static mrb_value app_mouse_position(mrb_state *mrb, mrb_value) { Vec2 mouse_pos = app_->mouse_position; mrb_value result = mrb_hash_new(mrb); @@ -663,30 +1413,111 @@ static mrb_value app_mouse_position(mrb_state *mrb, mrb_value) { return result; } +static mrb_value app_exit(mrb_state *, mrb_value) { + app_->exit(); + return mrb_nil_value(); +} + inline void setup() { DEF_CLASS(Font, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); + ADD_METHOD(Font, "==", font_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Font, "eql?", font_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Font, "hash", font_hash, MRB_ARGS_NONE()); DEF_CLASS(Image, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + ADD_METHOD(Image, "==", image_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Image, "eql?", image_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Image, "hash", image_hash, MRB_ARGS_NONE()); DEF_CLASS(Animation, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + ADD_METHOD(Animation, "==", animation_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Animation, "eql?", animation_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Animation, "hash", animation_hash, MRB_ARGS_NONE()); + ADD_METHOD(Animation, "fps=", animation_fps_set, MRB_ARGS_REQ(1)); + ADD_METHOD(Animation, "fps", animation_fps_get, MRB_ARGS_NONE()); + ADD_METHOD(Animation, "frames=", animation_frames_set, MRB_ARGS_REQ(1)); + ADD_METHOD(Animation, "frames", animation_frames_get, MRB_ARGS_NONE()); - DEF_CLASS(ElementText, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); + DEF_CLASS(ElementText, MRB_ARGS_REQ(3) | MRB_ARGS_OPT(2)); + ADD_METHOD(ElementText, "==", element_text_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "eql?", element_text_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "hash", element_text_hash, MRB_ARGS_NONE()); ADD_METHOD(ElementText, "on_click", element_text_on_click, MRB_ARGS_BLOCK()); + ADD_METHOD(ElementText, "key=", element_text_key_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "key", element_text_key_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "color=", element_text_color_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "color", element_text_color_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "font=", element_text_font_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "font", element_text_font_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "params", element_text_params, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "params!", element_text_params_load, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "position=", element_text_position_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "position", element_text_position_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "scale=", element_text_scale_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "scale", element_text_scale_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "rotation=", element_text_rotation_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "rotation", element_text_rotation_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "alpha=", element_text_alpha_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "alpha", element_text_alpha_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "z=", element_text_z_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "z", element_text_z_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementText, "click_through=", element_text_click_through_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementText, "click_through", element_text_click_through_get, MRB_ARGS_NONE()); - DEF_CLASS(ElementRect, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); + DEF_CLASS(ElementRect, MRB_ARGS_REQ(3) | MRB_ARGS_OPT(1)); + ADD_METHOD(ElementRect, "==", element_rect_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "eql?", element_rect_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "hash", element_rect_hash, MRB_ARGS_NONE()); ADD_METHOD(ElementRect, "on_click", element_rect_on_click, MRB_ARGS_BLOCK()); + ADD_METHOD(ElementRect, "width=", element_rect_width_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "width", element_rect_width_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "height=", element_rect_height_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "height", element_rect_height_get, MRB_ARGS_NONE()); ADD_METHOD(ElementRect, "color=", element_rect_color_set, MRB_ARGS_REQ(1)); ADD_METHOD(ElementRect, "color", element_rect_color_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "position=", element_rect_position_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "position", element_rect_position_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "scale=", element_rect_scale_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "scale", element_rect_scale_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "rotation=", element_rect_rotation_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "rotation", element_rect_rotation_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "alpha=", element_rect_alpha_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "alpha", element_rect_alpha_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "z=", element_rect_z_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "z", element_rect_z_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementRect, "click_through=", element_rect_click_through_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementRect, "click_through", element_rect_click_through_get, MRB_ARGS_NONE()); DEF_CLASS(ElementImage, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); ADD_METHOD(ElementImage, "on_click", element_image_on_click, MRB_ARGS_BLOCK()); + ADD_METHOD(ElementImage, "==", element_image_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "eql?", element_image_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "hash", element_image_hash, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "animation=", element_image_animation_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "animation", element_image_animation_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "position=", element_image_position_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "position", element_image_position_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "scale=", element_image_scale_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "scale", element_image_scale_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "rotation=", element_image_rotation_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "rotation", element_image_rotation_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "alpha=", element_image_alpha_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "alpha", element_image_alpha_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "z=", element_image_z_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "z", element_image_z_get, MRB_ARGS_NONE()); + ADD_METHOD(ElementImage, "click_through=", element_image_click_through_set, MRB_ARGS_REQ(1)); + ADD_METHOD(ElementImage, "click_through", element_image_click_through_get, MRB_ARGS_NONE()); DEF_CLASS(Scene, MRB_ARGS_NONE()); + ADD_METHOD(Scene, "==", scene_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "eql?", scene_equal, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "hash", scene_hash, MRB_ARGS_NONE()); ADD_METHOD(Scene, "<<", scene_add_element, MRB_ARGS_REQ(1)); ADD_METHOD(Scene, "delete", scene_delete_element, MRB_ARGS_REQ(1)); ADD_METHOD(Scene, "remove", scene_delete_element, MRB_ARGS_REQ(1)); ADD_METHOD(Scene, "on_update", scene_on_update, MRB_ARGS_BLOCK()); ADD_METHOD(Scene, "elements_at", scene_elements_at, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "elements", scene_elements, MRB_ARGS_NONE()); DEF_MODULE(App); ADD_MODULE_METHOD(App, "run", app_run, MRB_ARGS_REQ(3)); @@ -694,7 +1525,9 @@ inline void setup() { ADD_MODULE_METHOD(App, "switch_to", app_switch_to, MRB_ARGS_REQ(1)); ADD_MODULE_METHOD(App, "localize", app_localize, MRB_ARGS_REQ(3)); ADD_MODULE_METHOD(App, "language=", app_language_set, MRB_ARGS_REQ(1)); + ADD_MODULE_METHOD(App, "language", app_language_get, MRB_ARGS_NONE()); ADD_MODULE_METHOD(App, "mouse_position", app_mouse_position, MRB_ARGS_NONE()); + ADD_MODULE_METHOD(App, "exit", app_exit, MRB_ARGS_NONE()); } } // namespace definitions diff --git a/include/binding/ruby.h b/include/binding/ruby.h index 1aa7bd8..8f60343 100644 --- a/include/binding/ruby.h +++ b/include/binding/ruby.h @@ -94,6 +94,8 @@ DEF_SYM(bold) DEF_SYM(italic) DEF_SYM(underline) DEF_SYM(position) +DEF_SYM(rotation) +DEF_SYM(scale) DEF_SYM(alpha) DEF_SYM(fps) DEF_SYM(click_through) diff --git a/include/localization/localization.h b/include/localization/localization.h index 9d015ff..f6cf1c2 100644 --- a/include/localization/localization.h +++ b/include/localization/localization.h @@ -17,20 +17,87 @@ inline JoinedKey join_keys(LanguageCode lang_code, Key loc_key) { return (static_cast(lang_code) << 16) | loc_key; } -inline std::unordered_map localized_strings; +struct Segment { + bool is_var; + std::string text; +}; -inline std::string get(Key key) { - auto it = localized_strings.find(join_keys(current_language, key)); - if (it != localized_strings.end()) +inline std::unordered_map> localized_strings; +inline std::unordered_map var_sym_cache; + +inline mrb_sym get_var_sym(mrb_state *mrb, const std::string &name) { + auto it = var_sym_cache.find(name); + if (it != var_sym_cache.end()) return it->second; - for (const auto &[joined, value] : localized_strings) { - Key k = (Key)(joined & 0xFFFF); - if (k == key) - return value; + mrb_sym sym = mrb_intern_cstr(mrb, name.c_str()); + var_sym_cache[name] = sym; + return sym; +} + +inline std::vector parse_localized(const std::string &input) { + std::vector out; + + size_t i = 0; + while (i < input.size()) { + size_t start = input.find("%{", i); + + if (start == std::string::npos) { + out.push_back({false, input.substr(i)}); + break; + } + + if (start > i) { + out.push_back({false, input.substr(i, start - i)}); + } + + size_t end = input.find("}", start); + if (end == std::string::npos) { + // malformed fallback + out.push_back({false, input.substr(start)}); + break; + } + + std::string var = input.substr(start + 2, end - (start + 2)); + out.push_back({true, var}); + + i = end + 1; } - return "Localization not set!"; + return out; +} + +inline void set(LanguageCode lang_code, Key key, const std::string &text) { + localized_strings[join_keys(lang_code, key)] = parse_localized(text); +} + +inline std::string get(mrb_state *mrb, Key key, mrb_value hash) { + auto it = localized_strings.find(join_keys(current_language, key)); + if (it == localized_strings.end()) + return "Localization not set!"; + + const auto &entry = it->second; + + std::string result; + result.reserve(10); + + for (const auto &seg : entry) { + if (!seg.is_var) { + result += seg.text; + } else { + mrb_sym sym = get_var_sym(mrb, seg.text); + if (mrb_nil_p(hash) || mrb_nil_p(mrb_hash_get(mrb, hash, mrb_symbol_value(sym)))) { + result += "%{" + seg.text + "}"; + continue; + } + mrb_value val = mrb_hash_get(mrb, hash, mrb_symbol_value(sym)); + if (mrb_nil_p(val)) + continue; + result += mrb_str_to_cstr(mrb, val); + } + } + + return result; } inline LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym) { @@ -49,6 +116,13 @@ inline LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym) { return code; } +inline mrb_sym lang_to_sym(mrb_state *mrb, LanguageCode code) { + char s[5] = {0}; + for (int i = 0; i < 4; ++i) + s[i] = (code >> (8 * (3 - i))) & 0xFF; + return mrb_intern_cstr(mrb, s); +} + inline Key key_from_sym(mrb_state *mrb, mrb_sym sym) { const char *key_name = mrb_sym2name(mrb, sym); diff --git a/include/utils.h b/include/utils.h index 6857558..9d330eb 100644 --- a/include/utils.h +++ b/include/utils.h @@ -9,7 +9,11 @@ #define UNUSED(x) (void)(x) struct Color { - uint8_t r, g, b = 0, a = 255; + uint8_t r, g, b = 0; + + bool operator!=(const Color &other) const { + return r != other.r || g != other.g || b != other.b; + } }; template diff --git a/include/window/window.h b/include/window/window.h index adf7d1c..7b321d9 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -83,8 +83,6 @@ struct Event { struct Animation { std::vector frames; float frame_rate; - bool loop; - int reference_count = 0; ~Animation() { for (uint64_t frame_id : frames) @@ -104,20 +102,20 @@ public: ~Window(); // Rendering functions - uint64_t load_image(const char *path, float alpha = 1.0f, bool pixel_art = false); + uint64_t load_image(const char *path, bool pixel_art); Vec2 get_image_size(uint64_t image_id); - uint64_t load_font(const char *path, int font_size, bool bold = false, bool italic = false, bool underline = false, bool pixel_art = false); + uint64_t load_font(const char *path, int font_size, bool bold, bool italic, bool underline, bool pixel_art); // text is copied internally, so it can be free'd by the caller after the text is created. uint64_t create_text(uint64_t font_id, const char *text, uint32_t length, Color color); Vec2 get_text_size(uint64_t text_id); - bool write(uint64_t text_id, Vec2 position, float z, float angle = 0.0f, Vec2 scale = {1.0f, 1.0f}); + bool write(uint64_t text_id, Vec2 position, float z, float angle, Vec2 scale, float alpha); - bool draw(uint64_t image_id, Vec2 position, float z, float angle = 0.0f, Vec2 scale = {1.0f, 1.0f}); + bool draw(uint64_t image_id, Vec2 position, float z, float angle, Vec2 scale, float alpha); - bool draw_rect(Rect rect, Color color, float z, float angle = 0.0f); + bool draw_rect(Rect rect, Color color, float z, float angle, Vec2 scale, float alpha); void render(); // the rest of the calls only build the instruction list, this actually renders everything added to the screen (following z order) @@ -170,6 +168,7 @@ private: float z; float angle; Vec2 scale; + float alpha; union { struct { diff --git a/samples/button.rb b/samples/button.rb index 56ccf60..622af5d 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -4,33 +4,35 @@ main_scene = Scene.new default_font = Font.new "assets/fonts/charybdis.ttf", 24, pixel_art: true -bg_image1 = Image.new "assets/images/menu_bg.png", alpha: 0.7, pixel_art: true -bg_image2 = Image.new "assets/images/game_over_dead_bg.png", alpha: 1, pixel_art: true +bg_image1 = Image.new "assets/images/menu_bg.png", pixel_art: true +bg_image2 = Image.new "assets/images/game_over_dead_bg.png", pixel_art: true bg_animation = Animation.new [bg_image1, bg_image2], fps: 5 App.language = :en -App.localize(:en, :btn_text, "Click me!") +App.localize(:en, :btn_text, "Click %{name}!") -text = ElementText.new :btn_text, default_font, position: {x: 50, y: 50}, z: 1, click_through: true +text = ElementText.new :btn_text, default_font, 0xFFFFFF, {name: "Me"}, position: {x: 50, y: 50}, z: 1, click_through: true, alpha: 1 -rect = ElementRect.new({x: 50, y: 50, w: 100, h: 25}, 0xFF0000FF, z: 0) +rect = ElementRect.new 100, 25, 0xFF0000, position: {x: 50, y: 50}, z: 0, rotation: 60, scale: { x: 1.5, y: 1 }, alpha: 0.4 -image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_through: true +image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_through: true, alpha: 1 rect.on_click do - rect.color = rect.color == 0xFF0000FF ? - 0x00FF00FF - : - 0xFF0000FF + rect.color = rect.color == 0xFF0000 ? + 0x00FF00 : + 0xFF0000 + text.params[:name] = text.params[:name] == "Me" ? + "You" : + "Me" + text.params! end + =begin input handling: -on_click - exists to simplify on elements, not the standard way. - App.map_key(:space, :jump) # maps the space key to a :jump action (multiple keys can be mapped to the same action) # all events here are in english ones, (so need to find which english key corresponds to the key in the current language of the keyboard) key can be one of [:a..:z, :0..:9, :space, :enter, :shift, :ctrl, :alt, :tab, :backspace, :escape, :up, :down, :left, :right, :middle] @@ -58,16 +60,9 @@ App.text_input # returns the text typed this frame as a string (useful for text App.stop_text_input! -#### - -also in a scene you can use (with click_though semantics): - -main_scene.elements_at(x, y) =end - - main_scene.on_update do |delta_time| # puts "Delta time: #{delta_time}ms" # commented out to avoid spamming the console, but you can uncomment to see the delta time being printed every frame @@ -76,11 +71,11 @@ main_scene.on_update do |delta_time| el = main_scene.elements_at(App.mouse_position) for elem in el if elem == image_element - puts "Hovering over image element" + #puts "Hovering over image element" elsif elem == rect - puts "Hovering over rect element" + #puts "Hovering over rect element" elsif elem == text - puts "Hovering over text element" + #puts "Hovering over text element" end end diff --git a/src/window/window.cc b/src/window/window.cc index 5179b39..2632e75 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -56,7 +56,7 @@ Window::~Window() { SDL_Quit(); } -uint64_t Window::load_image(const char *path, float alpha, bool pixel_art) { +uint64_t Window::load_image(const char *path, bool pixel_art) { SDL_Texture *texture = IMG_LoadTexture(renderer, path); if (pixel_art) { @@ -68,8 +68,6 @@ uint64_t Window::load_image(const char *path, float alpha, bool pixel_art) { return UINT64_MAX; } - SDL_SetTextureAlphaMod(texture, (uint8_t)(alpha * 255)); - float w, h; SDL_GetTextureSize(texture, &w, &h); @@ -119,7 +117,7 @@ uint64_t Window::create_text(uint64_t font_id, const char *text, uint32_t length return UINT64_MAX; } - SDL_Color sdl_color = {color.r, color.g, color.b, color.a}; + SDL_Color sdl_color = {color.r, color.g, color.b, 255}; SDL_Surface *surface = TTF_RenderText_Solid(font_pool[font_id]->font, text, length, sdl_color); if (surface == nullptr) { @@ -157,7 +155,7 @@ Vec2 Window::get_text_size(uint64_t text_id) { return text_pool[text_id]->size; } -bool Window::write(uint64_t text_id, Vec2 position, float z, float angle, Vec2 scale) { +bool Window::write(uint64_t text_id, Vec2 position, float z, float angle, Vec2 scale, float alpha) { if (!text_pool.is_valid(text_id)) { fprintf(stderr, "(Write) Invalid text ID: %llu\n", (unsigned long long)text_id); return false; @@ -168,6 +166,7 @@ bool Window::write(uint64_t text_id, Vec2 position, float z, float angle, .z = z, .angle = angle, .scale = scale, + .alpha = alpha, .draw_text = { .text_id = text_id, .position = position, @@ -177,7 +176,7 @@ bool Window::write(uint64_t text_id, Vec2 position, float z, float angle, return true; }; -bool Window::draw(uint64_t image_id, Vec2 position, float z, float angle, Vec2 scale) { +bool Window::draw(uint64_t image_id, Vec2 position, float z, float angle, Vec2 scale, float alpha) { if (!image_pool.is_valid(image_id)) { fprintf(stderr, "Invalid image ID: %llu\n", (unsigned long long)image_id); return false; @@ -188,6 +187,7 @@ bool Window::draw(uint64_t image_id, Vec2 position, float z, float angle, .z = z, .angle = angle, .scale = scale, + .alpha = alpha, .draw_image = { .image_id = image_id, .position = position, @@ -197,12 +197,13 @@ bool Window::draw(uint64_t image_id, Vec2 position, float z, float angle, return true; }; -bool Window::draw_rect(Rect rect, Color color, float z, float angle) { +bool Window::draw_rect(Rect rect, Color color, float z, float angle, Vec2 scale, float alpha) { render_instructions.push_back( {.type = RenderInstruction::DRAW_RECT, .z = z, .angle = angle, - .scale = {1.0f, 1.0f}, + .scale = scale, + .alpha = alpha, .draw_rect = { .rect = rect, .color = color, @@ -276,6 +277,8 @@ void Window::render() { dst_rect.w = scale_x * text->size.x; dst_rect.h = scale_y * text->size.y; + SDL_SetTextureAlphaModFloat(texture, instruction.alpha); + SDL_RenderTextureRotated( renderer, texture, @@ -306,6 +309,8 @@ void Window::render() { dst_rect.w = scale_x * image->size.x; dst_rect.h = scale_y * image->size.y; + SDL_SetTextureAlphaModFloat(image->texture, instruction.alpha); + SDL_RenderTextureRotated( renderer, image->texture, @@ -321,8 +326,8 @@ void Window::render() { SDL_FRect dst_rect; dst_rect.x = instruction.draw_rect.rect.x() * scale + offset.x; dst_rect.y = instruction.draw_rect.rect.y() * scale + offset.y; - dst_rect.w = instruction.draw_rect.rect.w() * scale; - dst_rect.h = instruction.draw_rect.rect.h() * scale; + dst_rect.w = instruction.draw_rect.rect.w() * scale * instruction.scale.x; + dst_rect.h = instruction.draw_rect.rect.h() * scale * instruction.scale.y; SDL_SetTextureColorMod( white_tex, @@ -331,7 +336,7 @@ void Window::render() { instruction.draw_rect.color.b ); - SDL_SetTextureAlphaMod(white_tex, instruction.draw_rect.color.a); + SDL_SetTextureAlphaModFloat(white_tex, instruction.alpha); SDL_RenderTextureRotated( renderer,