diff --git a/include/app/app.h b/include/app/app.h index 3117956..b24ac43 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -1,234 +1,12 @@ #ifndef APP_H #define APP_H +#include "app/scene.h" #include "bindings/ruby.h" -#include "localization/localization.h" #include "utils.h" #include "window/window.h" -enum Type { IMAGE, - TEXT, - RECT }; - -enum ClickMode { PASS, - BLOCK, - IGNORE }; - namespace app { -struct Element { - Type type; - Vec2 position = {0, 0}; - float rotation = 0; - Vec2 pivot = {0, 0}; - Vec2 scale = {1, 1}; - float alpha = 1.0f; - float z = 0; - ClickMode click_mode = ClickMode::BLOCK; - Ruby::Block on_click; - - Element(Type type) : type(type) {} - virtual ~Element() = default; - virtual void render(Window &window, uint64_t abs_time) = 0; - virtual Vec2 size() = 0; - - void click() { - on_click.call(); - } -}; - -struct EImage : Element { - uint64_t animation_id = UINT64_MAX; - - EImage(uint64_t animation_id) : Element(Type::IMAGE), animation_id(animation_id) {} - - ~EImage() { - if (animation_id != UINT64_MAX) - animation_pool.release(animation_id); - } - - Vec2 size() override { - Animation &animation = *animation_pool[animation_id]; - if (animation.frames.empty()) - return {0, 0}; - return window.get_image_size(animation.frames[0]); - } - - void render(Window &window, uint64_t abs_time) override { - Animation &animation = *animation_pool[animation_id]; - if (animation.frames.empty()) - return; - int frame_index = (int)((abs_time * animation.frame_rate) / 1e9) % animation.frames.size(); - window.draw(animation.frames[frame_index], position, z, rotation, pivot, scale, alpha); - } -}; - -struct EText : Element { - Localization::Key key; - uint64_t font_id = UINT64_MAX; - Color color = {255, 255, 255}; - std::unordered_map params; - - ~EText() { - if (text_id != UINT64_MAX) - text_pool.release(text_id); - if (font_id != UINT64_MAX) - font_pool.release(font_id); - } - - EText(Localization::Key key, uint64_t font_id, Color color, std::unordered_map params) - : Element(Type::TEXT), key(key), font_id(font_id), color(color), params(params) {} - - Vec2 size() override { - if (text_id == UINT64_MAX) - return {0, 0}; - return window.get_text_size(text_id); - } - - void reload_text() { - if (text_id != UINT64_MAX) { - text_pool.release(text_id); - text_id = UINT64_MAX; - } - } - - void render(Window &window, uint64_t) override { - if ( - last_language != Localization::current_language - || last_key != key || text_id == UINT64_MAX - || last_color != color - || params_changed - ) { - std::string localized_str = Localization::get(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; - params_changed = false; - } - window.write(text_id, position, z, rotation, pivot, scale, alpha); - } - -private: - uint64_t text_id = UINT64_MAX; - Color last_color = color; - Localization::LanguageCode last_language = UINT32_MAX; - Localization::Key last_key = UINT16_MAX; - bool params_changed = true; -}; - -struct ERect : Element { - Vec2 shape = {0, 0}; - Color color = {255, 255, 255}; - Vec2 size() override { - return shape; - }; - - ERect(Vec2 shape, Color color) : Element(Type::RECT), shape(shape), color(color) {} - - void render(Window &window, uint64_t) override { - window.draw_rect(Rect::from(position, shape), color, z, rotation, pivot, scale, alpha); - } -}; - -inline Pool element_pool; - -struct Scene { - std::vector element_ids; - Ruby::Block update; - - ~Scene() { - for (const uint64_t &id : element_ids) - element_pool.release(id); - } - - void add_element(uint64_t element_id) { - auto it = std::lower_bound( - element_ids.begin(), - element_ids.end(), - element_id, - [](auto &a, auto &b) { - auto e_a = element_pool[a]; - auto e_b = element_pool[b]; - return e_a->z < e_b->z || (e_a->z == e_b->z && a < b); - } - ); - element_pool.use(element_id); - element_ids.insert(it, element_id); - } - - void delete_element(uint64_t element_id) { - auto it = std::find(element_ids.begin(), element_ids.end(), element_id); - if (it != element_ids.end()) { - element_pool.release(element_id); - element_ids.erase(it); - } - } - - void update_scene(mrb_value dt_val) { - if (mrb_nil_p(update.proc)) - return; - update.call(1, &dt_val); - } - - void click(Vec2 position) { - std::vector clicked_elements = elements_at(position); - for (const uint64_t &id : clicked_elements) - element_pool[id]->click(); - } - - void render(Window &window, uint64_t abs_time) { - for (const uint64_t &e : element_ids) - element_pool[e]->render(window, abs_time); - } - - std::vector elements_at(Vec2 position) { - std::vector result; - - for (auto it = element_ids.rbegin(); it != element_ids.rend(); ++it) { - Element *e = element_pool[*it]; - Vec2 size = e->size(); - Rect rect = Rect::from(size); - if (rect.contains(world_to_local(position, e))) { - if (e->click_mode == ClickMode::PASS) { - result.push_back(*it); - } else if (e->click_mode == ClickMode::BLOCK) { - result.push_back(*it); - break; - } else { - continue; - } - } - } - - return result; - } - - Vec2 world_to_local(Vec2 p, Element *e) { - Vec2 size = e->size(); - Vec2 pivot = e->pivot * size * e->scale; - p = p - e->position; - - float rad = -e->rotation * (M_PI / 180.0f); - float c = cos(rad); - float s = sin(rad); - - p = p - pivot; - p = { - p.x * c - p.y * s, - p.x * s + p.y * c - }; - - p = p + pivot; - p = p / e->scale; - - return p; - } -}; - -inline Pool scene_pool; - struct App { uint64_t current_scene_id = 0; bool running = true; @@ -240,113 +18,17 @@ struct App { std::string text = ""; Ruby::Block update; - void exit() { - running = false; - } + App(Vec2 size, const char *title); - void switch_to_scene(uint64_t scene_id) { - scene_pool.release(current_scene_id); - scene_pool.use(scene_id); - current_scene_id = scene_id; - } + void exit(); - App(Vec2 size, const char *title) { - window.update(size, title); - Scene *loading_scene = new Scene{}; - current_scene_id = scene_pool.acquire(loading_scene); - } + void switch_to_scene(uint64_t scene_id); - void set_fps(float fps) { - this->fps = fps; - target_frame_time = 1e9 / fps; - } + void set_fps(float fps); - uint64_t get_fps() const { - return fps; - } + uint64_t get_fps() const; - void loop() { - uint64_t frame_start = window.get_time(); - uint64_t frame_end = window.get_time(); - uint64_t previous_frame_end = 0; - - while (running) { - frame_start = window.get_time(); - uint64_t frame_time = frame_start - frame_end; - - if (frame_time <= target_frame_time) { - struct timespec req; - req.tv_sec = 0; - req.tv_nsec = target_frame_time - frame_time; - nanosleep(&req, nullptr); - } - - previous_frame_end = frame_end; - frame_end = window.get_time(); - - float dt = frame_end - previous_frame_end; - - Scene *scene = scene_pool[current_scene_id]; - - pressed_keys.clear(); - released_keys.clear(); - scroll = {0, 0}; - text = ""; - - Event e; - while (window.poll(e)) { - switch (e.type) { - case Event::QUIT: - running = false; - break; - case Event::MOUSE_BUTTON_DOWN: - scene->click(e.mouse_button.position); - down_keys[e.mouse_button.button] = true; - pressed_keys.push_back(e.mouse_button.button); - break; - case Event::MOUSE_BUTTON_UP: - down_keys[e.mouse_button.button] = false; - released_keys.push_back(e.mouse_button.button); - break; - case Event::MOUSE_MOVE: - mouse_position = e.mouse_move.position; - break; - case Event::MOUSE_LEAVE: - mouse_position = {-1, -1}; - break; - case Event::KEY_DOWN: - down_keys[e.key.key] = true; - pressed_keys.push_back(e.key.key); - break; - case Event::KEY_UP: - down_keys[e.key.key] = false; - released_keys.push_back(e.key.key); - break; - case Event::SCROLL: - scroll = e.scroll; - break; - case Event::TEXT_INPUT: - text += *e.text_input; - delete e.text_input; - break; - default: - break; - } - } - - int ai = mrb_gc_arena_save(Ruby::mrb); - mrb_value dt_val = mrb_float_value(Ruby::mrb, dt / 1e9); - update.call(1, &dt_val); - scene->update_scene(dt_val); - mrb_gc_arena_restore(Ruby::mrb, ai); - - scene->render(window, window.get_time()); - - window.render(); - - window.clear(); - } - } + void loop(); private: uint64_t fps = 60; diff --git a/include/app/elements.h b/include/app/elements.h new file mode 100644 index 0000000..3178291 --- /dev/null +++ b/include/app/elements.h @@ -0,0 +1,87 @@ +#ifndef ELEMENTS_H +#define ELEMENTS_H + +#include "bindings/ruby.h" +#include "localization/localization.h" +#include "utils.h" +#include "window/window.h" + +enum Type { IMAGE, + TEXT, + RECT }; + +enum ClickMode { PASS, + BLOCK, + IGNORE }; + +namespace app { +struct Element { + Type type; + Vec2 position = {0, 0}; + float rotation = 0; + Vec2 pivot = {0, 0}; + Vec2 scale = {1, 1}; + float alpha = 1.0f; + float z = 0; + ClickMode click_mode = ClickMode::BLOCK; + Ruby::Block on_click; + + Element(Type type) : type(type) {} + virtual ~Element() = default; + virtual void render(uint64_t) = 0; + virtual Vec2 size() = 0; + + void click(); +}; + +struct EImage : Element { + uint64_t animation_id = UINT64_MAX; + + EImage(uint64_t animation_id) : Element(Type::IMAGE), animation_id(animation_id) {} + + ~EImage(); + + Vec2 size() override; + + void render(uint64_t) override; +}; + +struct EText : Element { + Localization::Key key; + uint64_t font_id = UINT64_MAX; + Color color = {255, 255, 255}; + std::unordered_map params; + + ~EText(); + + EText(Localization::Key key, uint64_t font_id, Color color, std::unordered_map params) + : Element(Type::TEXT), key(key), font_id(font_id), color(color), params(params) {} + + Vec2 size() override; + + void reload_text(); + + void render(uint64_t) override; + +private: + uint64_t text_id = UINT64_MAX; + Color last_color = color; + Localization::LanguageCode last_language = UINT32_MAX; + Localization::Key last_key = UINT16_MAX; + bool params_changed = true; +}; + +struct ERect : Element { + Vec2 shape = {0, 0}; + Color color = {255, 255, 255}; + Vec2 size() override; + + ERect(Vec2 shape, Color color) : Element(Type::RECT), shape(shape), color(color) {} + + void render(uint64_t) override; +}; + +inline Pool element_pool; +} // namespace app + +#endif \ No newline at end of file diff --git a/include/app/scene.h b/include/app/scene.h new file mode 100644 index 0000000..18e3522 --- /dev/null +++ b/include/app/scene.h @@ -0,0 +1,33 @@ +#ifndef SCENE_H +#define SCENE_H + +#include "app/elements.h" +#include "bindings/ruby.h" +#include "utils.h" +#include "window/window.h" + +namespace app { +struct Scene { + std::vector element_ids; + Ruby::Block update; + + ~Scene(); + + void add_element(uint64_t element_id); + + void delete_element(uint64_t element_id); + void update_scene(mrb_value dt_val); + + void click(Vec2 position); + + void render(uint64_t abs_time); + + std::vector elements_at(Vec2 position); + + Vec2 world_to_local(Vec2 p, Element *e); +}; + +inline Pool scene_pool; +} // namespace app + +#endif \ No newline at end of file diff --git a/include/localization/localization.h b/include/localization/localization.h index 2825b9f..bf7adac 100644 --- a/include/localization/localization.h +++ b/include/localization/localization.h @@ -1,3 +1,6 @@ +#ifndef LOCALIZATION_H +#define LOCALIZATION_H + #include "pch.h" #include "utils.h" @@ -25,118 +28,21 @@ struct Segment { 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; - - 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 out; -} +std::vector parse_localized(const std::string &input); 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(Key key, const std::unordered_map ¶ms) { - auto it = localized_strings.find(join_keys(current_language, key)); - if (it == localized_strings.end()) - return "Localization not set!"; +mrb_sym get_var_sym(mrb_state *mrb, const std::string &name); - const auto &entry = it->second; +std::string get(Key key, const std::unordered_map ¶ms); - std::string result; - result.reserve(10); +LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym); - for (const auto &seg : entry) { - if (!seg.is_var) { - result += seg.text; - } else { - auto param_it = params.find(seg.text); - if (param_it == params.end()) { - result += "%{" + seg.text + "}"; - continue; - } - result += param_it->second; - } - } +mrb_sym lang_to_sym(mrb_state *mrb, LanguageCode code); - return result; -} +std::string get_key_name(Key key); +} // namespace Localization -inline LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym) { - const char *s = mrb_sym2name(mrb, sym); - size_t len = strlen(s); - - if (len == 0 || len > 4) - return 0; - - LanguageCode code = 0; - - for (size_t i = 0; i < len; ++i) { - code |= (static_cast(s[i]) << (8 * (3 - i))); - } - - 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); - - auto it = localization_key_map.find(key_name); - if (it != localization_key_map.end()) - return it->second; - - Key new_id = (Key)localization_key_map.size(); - localization_key_map[key_name] = new_id; - return new_id; -} - -// This is only used for debugging and inspect output, so performance is not a concern -inline std::string get_key_name(Key key) { - for (const auto &[str, k] : localization_key_map) - if (k == key) - return str; - return "Unknown Key"; -} -} // namespace Localization \ No newline at end of file +#endif \ No newline at end of file diff --git a/include/window/keybinds.h b/include/window/keybinds.h new file mode 100644 index 0000000..2bd9bc3 --- /dev/null +++ b/include/window/keybinds.h @@ -0,0 +1,221 @@ +#ifndef KEYBINDS_H +#define KEYBINDS_H + +#include "pch.h" +#include "utils.h" + +enum Key : uint16_t { + Unknown = SDL_SCANCODE_UNKNOWN, + + A = SDL_SCANCODE_A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + Num0 = SDL_SCANCODE_0, + Num1, + Num2, + Num3, + Num4, + Num5, + Num6, + Num7, + Num8, + Num9, + + Space = SDL_SCANCODE_SPACE, + Enter = SDL_SCANCODE_RETURN, + Escape = SDL_SCANCODE_ESCAPE, + Tab = SDL_SCANCODE_TAB, + Backspace = SDL_SCANCODE_BACKSPACE, + + LeftShift = SDL_SCANCODE_LSHIFT, + RightShift = SDL_SCANCODE_RSHIFT, + LeftCtrl = SDL_SCANCODE_LCTRL, + RightCtrl = SDL_SCANCODE_RCTRL, + LeftAlt = SDL_SCANCODE_LALT, + RightAlt = SDL_SCANCODE_RALT, + CapsLock = SDL_SCANCODE_CAPSLOCK, + + Up = SDL_SCANCODE_UP, + Down = SDL_SCANCODE_DOWN, + Left = SDL_SCANCODE_LEFT, + Right = SDL_SCANCODE_RIGHT, + Home = SDL_SCANCODE_HOME, + End = SDL_SCANCODE_END, + PageUp = SDL_SCANCODE_PAGEUP, + PageDown = SDL_SCANCODE_PAGEDOWN, + + Delete = SDL_SCANCODE_DELETE, + Insert = SDL_SCANCODE_INSERT, + + Minus = SDL_SCANCODE_MINUS, + Equals = SDL_SCANCODE_EQUALS, + LeftBracket = SDL_SCANCODE_LEFTBRACKET, + RightBracket = SDL_SCANCODE_RIGHTBRACKET, + Semicolon = SDL_SCANCODE_SEMICOLON, + Apostrophe = SDL_SCANCODE_APOSTROPHE, + Grave = SDL_SCANCODE_GRAVE, + Comma = SDL_SCANCODE_COMMA, + Period = SDL_SCANCODE_PERIOD, + Slash = SDL_SCANCODE_SLASH, + Backslash = SDL_SCANCODE_NONUSBACKSLASH, + + F1 = SDL_SCANCODE_F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + MouseLeft = 513, + MouseMiddle, + MouseRight +}; + +constexpr std::pair KEY_NAMES[] = { + {"a", Key::A}, + {"b", Key::B}, + {"c", Key::C}, + {"d", Key::D}, + {"e", Key::E}, + {"f", Key::F}, + {"g", Key::G}, + {"h", Key::H}, + {"i", Key::I}, + {"j", Key::J}, + {"k", Key::K}, + {"l", Key::L}, + {"m", Key::M}, + {"n", Key::N}, + {"o", Key::O}, + {"p", Key::P}, + {"q", Key::Q}, + {"r", Key::R}, + {"s", Key::S}, + {"t", Key::T}, + {"u", Key::U}, + {"v", Key::V}, + {"w", Key::W}, + {"x", Key::X}, + {"y", Key::Y}, + {"z", Key::Z}, + + {"num0", Key::Num0}, + {"num1", Key::Num1}, + {"num2", Key::Num2}, + {"num3", Key::Num3}, + {"num4", Key::Num4}, + {"num5", Key::Num5}, + {"num6", Key::Num6}, + {"num7", Key::Num7}, + {"num8", Key::Num8}, + {"num9", Key::Num9}, + + {"space", Key::Space}, + {"enter", Key::Enter}, + {"escape", Key::Escape}, + {"tab", Key::Tab}, + {"backspace", Key::Backspace}, + + {"left_shift", Key::LeftShift}, + {"right_shift", Key::RightShift}, + {"left_ctrl", Key::LeftCtrl}, + {"right_ctrl", Key::RightCtrl}, + {"left_alt", Key::LeftAlt}, + {"right_alt", Key::RightAlt}, + {"capslock", Key::CapsLock}, + + {"up", Key::Up}, + {"down", Key::Down}, + {"left", Key::Left}, + {"right", Key::Right}, + {"home", Key::Home}, + {"end", Key::End}, + {"page_up", Key::PageUp}, + {"page_down", Key::PageDown}, + + {"delete", Key::Delete}, + {"insert", Key::Insert}, + + {"minus", Key::Minus}, + {"equals", Key::Equals}, + {"left_bracket", Key::LeftBracket}, + {"right_bracket", Key::RightBracket}, + {"semicolon", Key::Semicolon}, + {"apostrophe", Key::Apostrophe}, + {"grave", Key::Grave}, + {"comma", Key::Comma}, + {"period", Key::Period}, + {"slash", Key::Slash}, + {"backslash", Key::Backslash}, + + {"f1", Key::F1}, + {"f2", Key::F2}, + {"f3", Key::F3}, + {"f4", Key::F4}, + {"f5", Key::F5}, + {"f6", Key::F6}, + {"f7", Key::F7}, + {"f8", Key::F8}, + {"f9", Key::F9}, + {"f10", Key::F10}, + {"f11", Key::F11}, + {"f12", Key::F12}, + + {"mouse_left", Key::MouseLeft}, + {"mouse_middle", Key::MouseMiddle}, + {"mouse_right", Key::MouseRight} +}; + +inline Key get_key_from_name(std::string_view name) { + for (const auto &[key_name, key] : KEY_NAMES) + if (key_name == name) + return key; + return Key::Unknown; +} + +inline std::unordered_map action_key_map; + +inline void set_action_mapping(const std::string &action_name, const std::string &key_name) { + Key key = get_key_from_name(key_name); + if (key != Key::Unknown) + action_key_map[action_name] = key; +} + +inline Key get_key(std::string &name) { + auto it = action_key_map.find(name); + if (it != action_key_map.end()) + return it->second; + return get_key_from_name(name); +} + +#endif \ No newline at end of file diff --git a/include/window/window.h b/include/window/window.h index f64c9da..d725e29 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -3,6 +3,7 @@ #include "pch.h" #include "utils.h" +#include "window/keybinds.h" struct Image { SDL_Texture *texture; @@ -40,220 +41,6 @@ inline Pool font_pool; // Text pool inline Pool text_pool; -enum Key : uint16_t { - Unknown = SDL_SCANCODE_UNKNOWN, - - A = SDL_SCANCODE_A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - - Num0 = SDL_SCANCODE_0, - Num1, - Num2, - Num3, - Num4, - Num5, - Num6, - Num7, - Num8, - Num9, - - Space = SDL_SCANCODE_SPACE, - Enter = SDL_SCANCODE_RETURN, - Escape = SDL_SCANCODE_ESCAPE, - Tab = SDL_SCANCODE_TAB, - Backspace = SDL_SCANCODE_BACKSPACE, - - LeftShift = SDL_SCANCODE_LSHIFT, - RightShift = SDL_SCANCODE_RSHIFT, - LeftCtrl = SDL_SCANCODE_LCTRL, - RightCtrl = SDL_SCANCODE_RCTRL, - LeftAlt = SDL_SCANCODE_LALT, - RightAlt = SDL_SCANCODE_RALT, - CapsLock = SDL_SCANCODE_CAPSLOCK, - - Up = SDL_SCANCODE_UP, - Down = SDL_SCANCODE_DOWN, - Left = SDL_SCANCODE_LEFT, - Right = SDL_SCANCODE_RIGHT, - Home = SDL_SCANCODE_HOME, - End = SDL_SCANCODE_END, - PageUp = SDL_SCANCODE_PAGEUP, - PageDown = SDL_SCANCODE_PAGEDOWN, - - Delete = SDL_SCANCODE_DELETE, - Insert = SDL_SCANCODE_INSERT, - - Minus = SDL_SCANCODE_MINUS, - Equals = SDL_SCANCODE_EQUALS, - LeftBracket = SDL_SCANCODE_LEFTBRACKET, - RightBracket = SDL_SCANCODE_RIGHTBRACKET, - Semicolon = SDL_SCANCODE_SEMICOLON, - Apostrophe = SDL_SCANCODE_APOSTROPHE, - Grave = SDL_SCANCODE_GRAVE, - Comma = SDL_SCANCODE_COMMA, - Period = SDL_SCANCODE_PERIOD, - Slash = SDL_SCANCODE_SLASH, - Backslash = SDL_SCANCODE_NONUSBACKSLASH, - - F1 = SDL_SCANCODE_F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - - MouseLeft = 513, - MouseMiddle, - MouseRight -}; - -constexpr std::pair KEY_NAMES[] = { - {"a", Key::A}, - {"b", Key::B}, - {"c", Key::C}, - {"d", Key::D}, - {"e", Key::E}, - {"f", Key::F}, - {"g", Key::G}, - {"h", Key::H}, - {"i", Key::I}, - {"j", Key::J}, - {"k", Key::K}, - {"l", Key::L}, - {"m", Key::M}, - {"n", Key::N}, - {"o", Key::O}, - {"p", Key::P}, - {"q", Key::Q}, - {"r", Key::R}, - {"s", Key::S}, - {"t", Key::T}, - {"u", Key::U}, - {"v", Key::V}, - {"w", Key::W}, - {"x", Key::X}, - {"y", Key::Y}, - {"z", Key::Z}, - - {"num0", Key::Num0}, - {"num1", Key::Num1}, - {"num2", Key::Num2}, - {"num3", Key::Num3}, - {"num4", Key::Num4}, - {"num5", Key::Num5}, - {"num6", Key::Num6}, - {"num7", Key::Num7}, - {"num8", Key::Num8}, - {"num9", Key::Num9}, - - {"space", Key::Space}, - {"enter", Key::Enter}, - {"escape", Key::Escape}, - {"tab", Key::Tab}, - {"backspace", Key::Backspace}, - - {"left_shift", Key::LeftShift}, - {"right_shift", Key::RightShift}, - {"left_ctrl", Key::LeftCtrl}, - {"right_ctrl", Key::RightCtrl}, - {"left_alt", Key::LeftAlt}, - {"right_alt", Key::RightAlt}, - {"capslock", Key::CapsLock}, - - {"up", Key::Up}, - {"down", Key::Down}, - {"left", Key::Left}, - {"right", Key::Right}, - {"home", Key::Home}, - {"end", Key::End}, - {"page_up", Key::PageUp}, - {"page_down", Key::PageDown}, - - {"delete", Key::Delete}, - {"insert", Key::Insert}, - - {"minus", Key::Minus}, - {"equals", Key::Equals}, - {"left_bracket", Key::LeftBracket}, - {"right_bracket", Key::RightBracket}, - {"semicolon", Key::Semicolon}, - {"apostrophe", Key::Apostrophe}, - {"grave", Key::Grave}, - {"comma", Key::Comma}, - {"period", Key::Period}, - {"slash", Key::Slash}, - {"backslash", Key::Backslash}, - - {"f1", Key::F1}, - {"f2", Key::F2}, - {"f3", Key::F3}, - {"f4", Key::F4}, - {"f5", Key::F5}, - {"f6", Key::F6}, - {"f7", Key::F7}, - {"f8", Key::F8}, - {"f9", Key::F9}, - {"f10", Key::F10}, - {"f11", Key::F11}, - {"f12", Key::F12}, - - {"mouse_left", Key::MouseLeft}, - {"mouse_middle", Key::MouseMiddle}, - {"mouse_right", Key::MouseRight} -}; - -inline Key get_key_from_name(std::string_view name) { - for (const auto &[key_name, key] : KEY_NAMES) - if (key_name == name) - return key; - return Key::Unknown; -} - -inline std::unordered_map action_key_map; - -inline void set_action_mapping(const std::string &action_name, const std::string &key_name) { - Key key = get_key_from_name(key_name); - if (key != Key::Unknown) - action_key_map[action_name] = key; -} - -inline Key get_key(std::string &name) { - auto it = action_key_map.find(name); - if (it != action_key_map.end()) - return it->second; - return get_key_from_name(name); -} - struct Event { enum Type { NONE, diff --git a/samples/stress.rb b/samples/stress.rb index d506eb8..c87d036 100644 --- a/samples/stress.rb +++ b/samples/stress.rb @@ -10,13 +10,13 @@ font = Font.new "assets/fonts/charybdis.ttf", 24, pixel_art: true # --- Generate images --- images = [] -1.times do |i| +5.times do |i| images << Image.new("assets/images/menu_bg.png", pixel_art: true) end # --- Animation pool stress --- animations = [] -1.times do |i| +5.times do |i| anim = Animation.new(images.sample(5), fps: 5 + (i % 30)) animations << anim end diff --git a/src/app/app.cc b/src/app/app.cc new file mode 100644 index 0000000..56add03 --- /dev/null +++ b/src/app/app.cc @@ -0,0 +1,111 @@ +#include "app/app.h" + +namespace app { +void App::exit() { + running = false; +} + +void App::switch_to_scene(uint64_t scene_id) { + scene_pool.release(current_scene_id); + scene_pool.use(scene_id); + current_scene_id = scene_id; +} + +App::App(Vec2 size, const char *title) { + window.update(size, title); + Scene *loading_scene = new Scene{}; + current_scene_id = scene_pool.acquire(loading_scene); +} + +void App::set_fps(float fps) { + this->fps = fps; + target_frame_time = 1e9 / fps; +} + +uint64_t App::get_fps() const { + return fps; +} + +void App::loop() { + uint64_t frame_start = window.get_time(); + uint64_t frame_end = window.get_time(); + uint64_t previous_frame_end = 0; + + while (running) { + frame_start = window.get_time(); + uint64_t frame_time = frame_start - frame_end; + + if (frame_time <= target_frame_time) { + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = target_frame_time - frame_time; + nanosleep(&req, nullptr); + } + + previous_frame_end = frame_end; + frame_end = window.get_time(); + + float dt = frame_end - previous_frame_end; + + Scene *scene = scene_pool[current_scene_id]; + + pressed_keys.clear(); + released_keys.clear(); + scroll = {0, 0}; + text = ""; + + Event e; + while (window.poll(e)) { + switch (e.type) { + case Event::QUIT: + running = false; + break; + case Event::MOUSE_BUTTON_DOWN: + scene->click(e.mouse_button.position); + down_keys[e.mouse_button.button] = true; + pressed_keys.push_back(e.mouse_button.button); + break; + case Event::MOUSE_BUTTON_UP: + down_keys[e.mouse_button.button] = false; + released_keys.push_back(e.mouse_button.button); + break; + case Event::MOUSE_MOVE: + mouse_position = e.mouse_move.position; + break; + case Event::MOUSE_LEAVE: + mouse_position = {-1, -1}; + break; + case Event::KEY_DOWN: + down_keys[e.key.key] = true; + pressed_keys.push_back(e.key.key); + break; + case Event::KEY_UP: + down_keys[e.key.key] = false; + released_keys.push_back(e.key.key); + break; + case Event::SCROLL: + scroll = e.scroll; + break; + case Event::TEXT_INPUT: + text += *e.text_input; + delete e.text_input; + break; + default: + break; + } + } + + int ai = mrb_gc_arena_save(Ruby::mrb); + mrb_value dt_val = mrb_float_value(Ruby::mrb, dt / 1e9); + update.call(1, &dt_val); + scene->update_scene(dt_val); + mrb_gc_arena_restore(Ruby::mrb, ai); + + scene->render(window.get_time()); + + window.render(); + + window.clear(); + } +} +} // namespace app \ No newline at end of file diff --git a/src/app/elements.cc b/src/app/elements.cc new file mode 100644 index 0000000..3fddc66 --- /dev/null +++ b/src/app/elements.cc @@ -0,0 +1,77 @@ +#include "app/elements.h" + +namespace app { +void Element::click() { + on_click.call(); +} + +// EImage +EImage::~EImage() { + if (animation_id != UINT64_MAX) + animation_pool.release(animation_id); +} + +Vec2 EImage::size() { + Animation &animation = *animation_pool[animation_id]; + if (animation.frames.empty()) + return {0, 0}; + return window.get_image_size(animation.frames[0]); +} + +void EImage::render(uint64_t abs_time) { + Animation &animation = *animation_pool[animation_id]; + if (animation.frames.empty()) + return; + int frame_index = (int)((abs_time * animation.frame_rate) / 1e9) % animation.frames.size(); + window.draw(animation.frames[frame_index], position, z, rotation, pivot, scale, alpha); +} + +// EText +EText::~EText() { + if (text_id != UINT64_MAX) + text_pool.release(text_id); + if (font_id != UINT64_MAX) + font_pool.release(font_id); +} + +Vec2 EText::size() { + if (text_id == UINT64_MAX) + return {0, 0}; + return window.get_text_size(text_id); +} + +void EText::reload_text() { + if (text_id != UINT64_MAX) { + text_pool.release(text_id); + text_id = UINT64_MAX; + } +} + +void EText::render(uint64_t) { + if ( + last_language != Localization::current_language + || last_key != key || text_id == UINT64_MAX + || last_color != color + || params_changed + ) { + std::string localized_str = Localization::get(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; + params_changed = false; + } + window.write(text_id, position, z, rotation, pivot, scale, alpha); +} + +// ERect +Vec2 ERect::size() { + return shape; +}; + +void ERect::render(uint64_t) { + window.draw_rect(Rect::from(position, shape), color, z, rotation, pivot, scale, alpha); +} +} // namespace app \ No newline at end of file diff --git a/src/app/scene.cc b/src/app/scene.cc new file mode 100644 index 0000000..baddf39 --- /dev/null +++ b/src/app/scene.cc @@ -0,0 +1,91 @@ +#include "app/scene.h" + +namespace app { +Scene::~Scene() { + for (const uint64_t &id : element_ids) + element_pool.release(id); +} + +void Scene::add_element(uint64_t element_id) { + auto it = std::lower_bound( + element_ids.begin(), + element_ids.end(), + element_id, + [](auto &a, auto &b) { + auto e_a = element_pool[a]; + auto e_b = element_pool[b]; + return e_a->z < e_b->z || (e_a->z == e_b->z && a < b); + } + ); + element_pool.use(element_id); + element_ids.insert(it, element_id); +} + +void Scene::delete_element(uint64_t element_id) { + auto it = std::find(element_ids.begin(), element_ids.end(), element_id); + if (it != element_ids.end()) { + element_pool.release(element_id); + element_ids.erase(it); + } +} + +void Scene::update_scene(mrb_value dt_val) { + if (mrb_nil_p(update.proc)) + return; + update.call(1, &dt_val); +} + +void Scene::click(Vec2 position) { + std::vector clicked_elements = elements_at(position); + for (const uint64_t &id : clicked_elements) + element_pool[id]->click(); +} + +void Scene::render(uint64_t abs_time) { + for (const uint64_t &e : element_ids) + element_pool[e]->render(abs_time); +} + +std::vector Scene::elements_at(Vec2 position) { + std::vector result; + + for (auto it = element_ids.rbegin(); it != element_ids.rend(); ++it) { + Element *e = element_pool[*it]; + Vec2 size = e->size(); + Rect rect = Rect::from(size); + if (rect.contains(world_to_local(position, e))) { + if (e->click_mode == ClickMode::PASS) { + result.push_back(*it); + } else if (e->click_mode == ClickMode::BLOCK) { + result.push_back(*it); + break; + } else { + continue; + } + } + } + + return result; +} + +Vec2 Scene::world_to_local(Vec2 p, Element *e) { + Vec2 size = e->size(); + Vec2 pivot = e->pivot * size * e->scale; + p = p - e->position; + + float rad = -e->rotation * (M_PI / 180.0f); + float c = cos(rad); + float s = sin(rad); + + p = p - pivot; + p = { + p.x * c - p.y * s, + p.x * s + p.y * c + }; + + p = p + pivot; + p = p / e->scale; + + return p; +} +} // namespace app \ No newline at end of file diff --git a/src/localization/localization.cc b/src/localization/localization.cc new file mode 100644 index 0000000..a6bf5ad --- /dev/null +++ b/src/localization/localization.cc @@ -0,0 +1,114 @@ +#include "localization/localization.h" + +namespace Localization { +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; + + mrb_sym sym = mrb_intern_cstr(mrb, name.c_str()); + var_sym_cache[name] = sym; + return sym; +} + +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 out; +} + +std::string get(Key key, const std::unordered_map ¶ms) { + 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 { + auto param_it = params.find(seg.text); + if (param_it == params.end()) { + result += "%{" + seg.text + "}"; + continue; + } + result += param_it->second; + } + } + + return result; +} + +LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym) { + const char *s = mrb_sym2name(mrb, sym); + size_t len = strlen(s); + + if (len == 0 || len > 4) + return 0; + + LanguageCode code = 0; + + for (size_t i = 0; i < len; ++i) { + code |= (static_cast(s[i]) << (8 * (3 - i))); + } + + return code; +} + +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); +} + +Key key_from_sym(mrb_state *mrb, mrb_sym sym) { + const char *key_name = mrb_sym2name(mrb, sym); + + auto it = localization_key_map.find(key_name); + if (it != localization_key_map.end()) + return it->second; + + Key new_id = (Key)localization_key_map.size(); + localization_key_map[key_name] = new_id; + return new_id; +} + +// This is only used for debugging and inspect output, so performance is not a concern +std::string get_key_name(Key key) { + for (const auto &[str, k] : localization_key_map) + if (k == key) + return str; + return "Unknown Key"; +} +} // namespace Localization \ No newline at end of file