From 1baa2d66721e68ecc2e3e0654c8ff862a7a17368 Mon Sep 17 00:00:00 2001 From: zhenyan121 <104683324+zhenyan121@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:20:28 +0800 Subject: [PATCH] feature: screenshot (#39) * build: add stb image write lib * feat(renderer): add screenshot capture with F2 * feat(screenshot): add screenshot viewer scene Add SCREENSHOT scene and UI to browse screenshots from the main menu, with localization for English and Chinese. Also allow image loading by full path and replace scene-operation asserts with error logs. * feat(ui): handle window resize in ScreenshotUI * feat(ui): add ScrollView widget and dynamic layout on resize Introduces a ScrollView widget with mouse wheel support and viewport clipping. Credits and screenshot screens now use it and recompute layout on window resize. Offset storage switched from ivec2 to vec2 with new accessors. * feat(ui): add lightbox viewer for screenshots Add a lightbox to preview screenshots on click. Introduce Button::set_texture for image-backed buttons, add widget getters for window size and visibility, and block background input while the lightbox is open. Close the lightbox with Escape or a left-click. * feat(ui): add screenshot button to pause menu Add a button to open the screenshot scene from the pause menu. Also enable linear filtering on screenshot textures for smoother scaling. * feat(ui): add show in file manager button to screenshot view - Add Tools::open_file_manager helper with Windows and Linux support. - Ensure screenshot directory is created before scanning. - Add localization keys for en_US and zh_CN. * feat(screenshot-ui): add newest/oldest sort selector Add a combo button to the screenshot gallery to sort files by modification time (newest or oldest first). Store timestamp per image and rebuild layout when sort order changes. Include translations for en_US and zh_CN. * fix: correct argument typo and path/error handling Fix --enable-consolelog flag, screenshot filter bug, paths to C APIs, add includes and gitignore entries, and improve ShellExecuteW failure detection. * fix(audio): improve source management and OpenAL diagnostics Fix null deref when source pool is full, initialize m_using, and improve source state handling. Add OpenAL vendor/version/renderer logging and correct voice PCM span type. * fix(client_entity_manager): store find result before assert * fix(tools): switch datetime format to underscore-separated Use %Y-%m-%d_%H-%M-%S instead of %F %T to avoid colons and spaces, making output suitable for file names. * fix: initialize uninitialized member variables Add default member initializers across various classes to prevent undefined behavior from uninitialized fields, and initialize the neighbor biome array in the chunk generator. * feat(texture_manager): add check_exist option for image loading Add a `check_exist` parameter to `get_image_texture` and `load_image_texture`, enabling callers to skip file existence checks. Log an error and return nullptr when image data fails to load. Update screenshot UI to support uppercase image extensions and gracefully ignore unreadable images. --- .gitignore | 1 + assets/lang/en_US.json | 8 +- assets/lang/zh_CN.json | 8 +- include/Cubed/audio/audio_engine.hpp | 2 +- include/Cubed/audio/audio_source.hpp | 2 +- include/Cubed/camera.hpp | 8 +- include/Cubed/dev_panel.hpp | 2 +- include/Cubed/gameplay/block.hpp | 2 +- include/Cubed/gameplay/client_chunk.hpp | 28 +- include/Cubed/gameplay/client_world.hpp | 4 +- include/Cubed/gameplay/ecs/client_entity.hpp | 8 +- include/Cubed/gameplay/server_chunk.hpp | 2 +- .../Cubed/gameplay/server_entity_manager.hpp | 1 + include/Cubed/gameplay/server_player.hpp | 2 +- include/Cubed/gameplay/server_world.hpp | 2 +- include/Cubed/render/player_renderer.hpp | 4 +- include/Cubed/render/renderer.hpp | 11 +- include/Cubed/render/world_renderer.hpp | 6 +- include/Cubed/scene/scene.hpp | 3 +- include/Cubed/scene/screenshot_scene.hpp | 29 + include/Cubed/scene/world_scene.hpp | 2 +- include/Cubed/texture_manager.hpp | 8 +- include/Cubed/tools/file_utils.hpp | 66 + include/Cubed/tools/sensitive_filter.hpp | 4 +- include/Cubed/tools/time_tools.hpp | 12 + include/Cubed/ui/button.hpp | 1 + include/Cubed/ui/credits_ui.hpp | 2 + include/Cubed/ui/host_game_ui.hpp | 2 +- include/Cubed/ui/item_slot.hpp | 2 +- include/Cubed/ui/join_game_ui.hpp | 2 +- include/Cubed/ui/screenshot_ui.hpp | 49 + include/Cubed/ui/scroll_view.hpp | 35 + include/Cubed/ui/widget.hpp | 17 +- include/Cubed/window.hpp | 4 +- src/CMakeLists.txt | 3 + src/app.cpp | 2 +- src/audio/audio_engine.cpp | 15 +- src/audio/audio_source.cpp | 6 +- src/audio/source_pool.cpp | 11 +- src/gameplay/chunk_generator.cpp | 1 + src/gameplay/client_entity_manager.cpp | 3 +- src/render/model_manager.cpp | 2 +- src/render/renderer.cpp | 36 +- src/scene/scene_manager.cpp | 18 +- src/scene/screenshot_scene.cpp | 20 + src/scene/world_scene.cpp | 12 +- src/texture_manager.cpp | 16 +- src/tools/shader_tools.cpp | 2 +- src/ui/button.cpp | 13 +- src/ui/credits_ui.cpp | 59 +- src/ui/main_menu_ui_manager.cpp | 11 +- src/ui/pause_menu_ui_manager.cpp | 11 +- src/ui/screenshot_ui.cpp | 280 +++ src/ui/scroll_view.cpp | 112 ++ src/ui/widget.cpp | 12 +- third_party/stb/CMakeLists.txt | 1 + third_party/stb/stb_image_write.cpp | 2 + third_party/stb/stb_image_write.h | 1724 +++++++++++++++++ 58 files changed, 2612 insertions(+), 99 deletions(-) create mode 100644 include/Cubed/scene/screenshot_scene.hpp create mode 100644 include/Cubed/tools/file_utils.hpp create mode 100644 include/Cubed/ui/screenshot_ui.hpp create mode 100644 include/Cubed/ui/scroll_view.hpp create mode 100644 src/scene/screenshot_scene.cpp create mode 100644 src/ui/screenshot_ui.cpp create mode 100644 src/ui/scroll_view.cpp create mode 100644 third_party/stb/stb_image_write.cpp create mode 100644 third_party/stb/stb_image_write.h diff --git a/.gitignore b/.gitignore index 34d5687..e54445b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.pch *.sbr build/ +build_debug/ Build/ bin/ Debug/ diff --git a/assets/lang/en_US.json b/assets/lang/en_US.json index bd45502..b0e9e0c 100644 --- a/assets/lang/en_US.json +++ b/assets/lang/en_US.json @@ -4,6 +4,7 @@ "menu.main.join_game": "Join Game", "menu.main.credits": "Credits", "menu.main.quit": "Quit", + "menu.screenshot": "Screenshots", "menu.main.player_name": "Player: {name}", "menu.main.cubed_version": "Cubed: {version}", "menu.pause.pause_menu": "Pause Menu", @@ -46,5 +47,10 @@ "item.snowy_grass_block.name": "snowy grass block", "item.stone.name": "stone", "item.water.name": "water", - "item.pig_spawn_egg.name": "pig spawn egg" + "item.pig_spawn_egg.name": "pig spawn egg", + "screenshot.no_screenshot": "No Screenshot", + "button.show_in_file_manager": "Show In File Manager", + "file.sort_type": "File Sort Type: {type}", + "file.newest_first": "Newest First", + "file.oldest_first": "Oldest First" } \ No newline at end of file diff --git a/assets/lang/zh_CN.json b/assets/lang/zh_CN.json index 362fb53..0b13bda 100644 --- a/assets/lang/zh_CN.json +++ b/assets/lang/zh_CN.json @@ -4,6 +4,7 @@ "menu.main.join_game": "加入游戏", "menu.main.credits": "制作人员", "menu.main.quit": "退出", + "menu.screenshot": "屏幕截图", "menu.main.player_name": "玩家:{name}", "menu.main.cubed_version": "Cubed:{version}", "menu.pause.pause_menu": "暂停菜单", @@ -46,5 +47,10 @@ "item.snowy_grass_block.name": "覆雪草方块", "item.stone.name": "石头", "item.water.name": "水", - "item.pig_spawn_egg.name": "猪猪刷怪蛋" + "item.pig_spawn_egg.name": "猪猪刷怪蛋", + "screenshot.no_screenshot": "没有截图", + "button.show_in_file_manager": "在文件管理器中显示", + "file.sort_type": "文件排序方式:{type}", + "file.newest_first": "最新在前", + "file.oldest_first": "最旧在前" } \ No newline at end of file diff --git a/include/Cubed/audio/audio_engine.hpp b/include/Cubed/audio/audio_engine.hpp index 09b5a1c..1d77270 100644 --- a/include/Cubed/audio/audio_engine.hpp +++ b/include/Cubed/audio/audio_engine.hpp @@ -62,7 +62,7 @@ private: OpusDecoder* m_decoder{nullptr}; AudioRecording m_recording; std::weak_ptr m_client; - glm::vec3 listener_pos; + glm::vec3 listener_pos{0.0f}; std::unique_ptr m_bgm; FadeMap m_fade_map; SoundManager m_sounds; diff --git a/include/Cubed/audio/audio_source.hpp b/include/Cubed/audio/audio_source.hpp index b4a09de..99996bf 100644 --- a/include/Cubed/audio/audio_source.hpp +++ b/include/Cubed/audio/audio_source.hpp @@ -49,6 +49,6 @@ private: ALuint m_source = 0; float m_target_volume = 1.0f; float m_duration = 0.0f; - bool m_using; + bool m_using = false; }; } // namespace Cubed diff --git a/include/Cubed/camera.hpp b/include/Cubed/camera.hpp index 5fe9769..796671a 100644 --- a/include/Cubed/camera.hpp +++ b/include/Cubed/camera.hpp @@ -18,12 +18,12 @@ private: THIRD_PERSON_FRONT, }; - LocalPlayer* m_player; - float m_last_mouse_x, m_last_mouse_y; - glm::vec3 m_camera_pos; + LocalPlayer* m_player = nullptr; + float m_last_mouse_x = 0.0f, m_last_mouse_y = 0.0f; + glm::vec3 m_camera_pos{0.0f}; bool m_under_water = false; Perspective m_perspective = Perspective::FIRST_PERSON; - glm::vec3 m_front; + glm::vec3 m_front{0.0f}; glm::vec3 camera_collision(glm::vec3 start, glm::vec3 end, float radius = 0.2f); diff --git a/include/Cubed/dev_panel.hpp b/include/Cubed/dev_panel.hpp index 28a6935..84b95f9 100644 --- a/include/Cubed/dev_panel.hpp +++ b/include/Cubed/dev_panel.hpp @@ -42,7 +42,7 @@ private: WorldScene& m_world_scene; Config& m_config; ConfigView m_config_view; - LocalPlayer* m_player; + LocalPlayer* m_player = nullptr; PlayerProfile m_player_profile; bool m_need_save_config = false; bool m_gen_thread_running = true; diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index 8a8056f..4411097 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -22,7 +22,7 @@ struct Block : public BlockTexture {}; struct BlockRenderData { std::vector draw_face; - unsigned block_id; + unsigned block_id = 0; BlockRenderData() = default; BlockRenderData(const BlockRenderData&) = default; BlockRenderData& operator=(const BlockRenderData&) = default; diff --git a/include/Cubed/gameplay/client_chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp index 52a2952..5854c83 100644 --- a/include/Cubed/gameplay/client_chunk.hpp +++ b/include/Cubed/gameplay/client_chunk.hpp @@ -14,18 +14,18 @@ namespace Cubed { class ClientWorld; struct ChunkRenderSnapshot { - GLuint normal_vao; - size_t normal_vertices_count; - GLuint cross_vao; - size_t cross_vertices_count; - GLuint normal_discard_vao; - size_t normal_discard_vertices_count; - GLuint normal_blend_vao; - size_t normal_blend_vertices_count; - GLuint water_vao; - size_t water_vertices_count; - glm::vec3 center; - glm::vec3 half_extents; + GLuint normal_vao = 0; + size_t normal_vertices_count = 0; + GLuint cross_vao = 0; + size_t cross_vertices_count = 0; + GLuint normal_discard_vao = 0; + size_t normal_discard_vertices_count = 0; + GLuint normal_blend_vao = 0; + size_t normal_blend_vertices_count = 0; + GLuint water_vao = 0; + size_t water_vertices_count = 0; + glm::vec3 center{0.0f}; + glm::vec3 half_extents{0.0f}; }; class ClientChunk : public Chunk { public: @@ -98,7 +98,7 @@ private: std::atomic m_is_on_gen_vertex_data{false}; std::atomic m_biome = BiomeType::PLAIN; std::mutex m_vertexs_data_mutex; - ChunkPos m_chunk_pos; + ChunkPos m_chunk_pos{0, 0}; ClientWorld& m_world; // the index is a array of block id std::vector m_blocks; @@ -111,7 +111,7 @@ private: */ std::vector m_vertex_data; - ChunkRenderSnapshot m_render_snapshot; + ChunkRenderSnapshot m_render_snapshot{}; unsigned m_seed = 0; void clear_dirty(); diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 3040b22..bfa288c 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -107,7 +107,7 @@ public: private: struct VoiceMessage { std::string data; - glm::vec3 pos; + glm::vec3 pos{0.0f}; }; std::atomic m_is_pending_delete_queue_free{false}; @@ -127,7 +127,7 @@ private: struct PendingSound { std::string sound; - glm::vec3 sound_pos; + glm::vec3 sound_pos{0.0f}; }; static constexpr int WORLD_EXIT_TIMEOUT = 200; diff --git a/include/Cubed/gameplay/ecs/client_entity.hpp b/include/Cubed/gameplay/ecs/client_entity.hpp index 98df8af..8f7f899 100644 --- a/include/Cubed/gameplay/ecs/client_entity.hpp +++ b/include/Cubed/gameplay/ecs/client_entity.hpp @@ -7,15 +7,15 @@ namespace Cubed { struct BaseClientCreature { - Transform transform; + Transform transform{}; - WalkPose pose; + WalkPose pose{}; - ModelID model; + ModelID model = 0; }; struct SoundTime { - float next_call_time; + float next_call_time = 0.0f; }; struct ClientEntitySnapshot { diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp index 9eb9a9a..e8964b3 100644 --- a/include/Cubed/gameplay/server_chunk.hpp +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -67,7 +67,7 @@ private: ChunkPos m_chunk_pos; ServerWorld& m_world; - HeightMapArray m_heightmap; + HeightMapArray m_heightmap{}; // the index is a array of block id std::vector m_blocks; OptionalBlockVectorArray m_neightbor_blocks; diff --git a/include/Cubed/gameplay/server_entity_manager.hpp b/include/Cubed/gameplay/server_entity_manager.hpp index e5af786..f01e31b 100644 --- a/include/Cubed/gameplay/server_entity_manager.hpp +++ b/include/Cubed/gameplay/server_entity_manager.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Cubed { class ServerWorld; class Session; diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp index 9c28b79..c78e3a9 100644 --- a/include/Cubed/gameplay/server_player.hpp +++ b/include/Cubed/gameplay/server_player.hpp @@ -59,7 +59,7 @@ private: std::atomic m_chunk_task_id{0}; std::atomic m_yaw{0.0f}; std::atomic m_pitch{0.0f}; - std::atomic m_gait; + std::atomic m_gait{Gait::STOP}; mutable std::shared_mutex m_chunk_pos_mutex; ChunkPosSet m_player_chunk_pos_set; }; diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index e1e6930..8e4613d 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -115,7 +115,7 @@ public: private: enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE }; struct ChunkEntity { - ChunkState state; + ChunkState state = ChunkState::NONE; std::shared_ptr chunk; std::atomic ref_count = 0; ChunkEntity() = default; diff --git a/include/Cubed/render/player_renderer.hpp b/include/Cubed/render/player_renderer.hpp index d35e80e..40308e2 100644 --- a/include/Cubed/render/player_renderer.hpp +++ b/include/Cubed/render/player_renderer.hpp @@ -24,8 +24,8 @@ private: float tx = 0.0f, ty = 0.0f, tz = 0.0f; }; Renderer& m_renderer; - std::array m_vao; - std::array m_vbo; + std::array m_vao{}; + std::array m_vbo{}; bool m_inited{false}; std::array, BODY_PART_NUM> m_vertices; }; diff --git a/include/Cubed/render/renderer.hpp b/include/Cubed/render/renderer.hpp index b2deb3f..be03121 100644 --- a/include/Cubed/render/renderer.hpp +++ b/include/Cubed/render/renderer.hpp @@ -25,7 +25,7 @@ class DevPanel; class Renderer { public: constexpr static int NUM_VAO = 7; - + constexpr static const char* SCREENSHOT_PATH = "./screenshot"; Renderer(TextureManager& texture_manager, Config& config); ~Renderer(); void reload_config(); @@ -83,6 +83,8 @@ public: ModelRender& model_renderer(); + void save_screenshot(); + private: TextureManager& m_texture_manager; bool m_init = false; @@ -98,7 +100,9 @@ private: float m_window_width = 0.0f; float m_window_height = 0.0f; - glm::mat4 m_world_proj_matrix; + bool m_save_screenshot = false; + + glm::mat4 m_world_proj_matrix{0.0f}; std::unique_ptr m_sky_vbo; std::unique_ptr m_outline_indices_vbo; @@ -107,7 +111,7 @@ private: std::unique_ptr m_player_vbo; std::unique_ptr m_quad_vbo; - glm::mat4 m_ui_proj_matrix; + glm::mat4 m_ui_proj_matrix{0.0f}; ShaderManager m_shaders; /* @@ -129,6 +133,7 @@ private: void updata_framebuffer(int width, int height); void init_quad(); void init_text(); + void handle_screenshot(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/world_renderer.hpp b/include/Cubed/render/world_renderer.hpp index 51a2c1f..6842643 100644 --- a/include/Cubed/render/world_renderer.hpp +++ b/include/Cubed/render/world_renderer.hpp @@ -89,8 +89,8 @@ private: std::unique_ptr m_depth_map_fbo; std::unique_ptr m_depth_map_texture; - glm::vec3 m_blend_from_lightdir; - glm::vec3 m_blend_to_lightdir; + glm::vec3 m_blend_from_lightdir{0.0f}; + glm::vec3 m_blend_to_lightdir{0.0f}; float m_blend_t = 1.0f; bool m_blend_initialized = false; static constexpr float BLEND_DURATION = 0.15f; @@ -125,7 +125,7 @@ private: ParallelLight m_parallel_light; SkyUniform m_sky_uniform; - glm::mat4 view_matrix; + glm::mat4 view_matrix{0.0f}; const TextureManager& m_texture_manager; void day_night_calculation(ClientWorld& world); diff --git a/include/Cubed/scene/scene.hpp b/include/Cubed/scene/scene.hpp index b11f8c2..ce94a9b 100644 --- a/include/Cubed/scene/scene.hpp +++ b/include/Cubed/scene/scene.hpp @@ -9,7 +9,8 @@ enum class SceneType { CREDITS, SETTINGS, HOST_GAME, - JOIN_GAME + JOIN_GAME, + SCREENSHOT }; class Scene { diff --git a/include/Cubed/scene/screenshot_scene.hpp b/include/Cubed/scene/screenshot_scene.hpp new file mode 100644 index 0000000..49c0cef --- /dev/null +++ b/include/Cubed/scene/screenshot_scene.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Cubed/scene/scene.hpp" +#include "Cubed/ui/screenshot_ui.hpp" +namespace Cubed { +class SceneManager; +class ScreenshotScene : public Scene { +public: + ScreenshotScene(SceneManager& scene_manager); + ~ScreenshotScene(); + + ScreenshotScene(const ScreenshotScene&) = delete; + ScreenshotScene(ScreenshotScene&&) = delete; + ScreenshotScene& operator=(const ScreenshotScene&) = delete; + ScreenshotScene& operator=(ScreenshotScene&&) = delete; + + void update(float dt) override; + void render(Renderer& renderer) override; + bool handle_event(const Event& e) override; + void on_enter() override; + void on_leave() override; + void on_re_enter() override; + + SceneManager& scene_manager(); + +private: + SceneManager& m_scene_manager; + ScreenshotUI m_ui; +}; +} // namespace Cubed diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index 448142e..32f7016 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -62,7 +62,7 @@ private: WorldUIManager m_hud_ui; ErrorUI m_error_ui; const Argument& m_argument; - VoiceInputType m_input_type; + VoiceInputType m_input_type = VoiceInputType::OFF; RunMode m_runmode = RunMode::HYBRID; bool handle_mouse_move_event(const MouseMoveEvent& e) override; bool handle_mouse_button_event(const MouseButtonEvent& e) override; diff --git a/include/Cubed/texture_manager.hpp b/include/Cubed/texture_manager.hpp index b078106..c765bd9 100644 --- a/include/Cubed/texture_manager.hpp +++ b/include/Cubed/texture_manager.hpp @@ -20,7 +20,9 @@ public: const Texture* get_block_status_array() const; const Texture* get_texture_array() const; const Texture* get_cross_plane_array() const; - const Texture* get_image_texture(const std::string& path); + const Texture* get_image_texture(const std::string& path, + bool full_path = false, + bool check_exist = true); const Texture* get_pbr_texture() const; const ItemTextureMap& get_item_textures() const; const Texture* get_skin() const; @@ -49,7 +51,9 @@ private: void load_block_texture(unsigned block_id); void init_item_texture(); void load_cross_plane_texture(unsigned id); - const Texture* load_image_texture(const std::string& path); + const Texture* load_image_texture(const std::string& path, + bool full_path = false, + bool check_exist = true); void load_pbr_texture(unsigned id); void init_item(); void init_block(); diff --git a/include/Cubed/tools/file_utils.hpp b/include/Cubed/tools/file_utils.hpp new file mode 100644 index 0000000..9f095bb --- /dev/null +++ b/include/Cubed/tools/file_utils.hpp @@ -0,0 +1,66 @@ +#pragma once +#include "Cubed/tools/log.hpp" + +#include +#include + +#ifdef _WIN32 +// clang-format off +#include +#include +// clang-format on +#elif __linux__ +#include +#endif + +namespace Cubed::Tools { + +inline void open_file_manager(const std::filesystem::path& path) { +#ifdef _WIN32 + auto abs_path = std::filesystem::absolute(path); + if (!std::filesystem::exists(abs_path)) { + Logger::warn("Path does not exist: {}", abs_path.string()); + return; + } + HINSTANCE result = + ShellExecuteW(nullptr, L"open", abs_path.wstring().c_str(), nullptr, + nullptr, SW_SHOWNORMAL); + if ((INT_PTR)result <= 32) { + Logger::warn("ShellExecuteW failed for path: {}, error code: {}", + path.string(), (int)(INT_PTR)result); + } + +#elif __linux__ + + constexpr const char* FILE_MANAGERS[] = {"nautilus", "dolphin", "thunar", + "nemo", "pcmanfm"}; + + auto is_available = [](const char* exe) { + std::string cmd = std::string("command -v ") + exe + " >/dev/null 2>&1"; + return std::system(cmd.c_str()) == 0; + }; + + const std::string QUOTED = "\"" + path.string() + "\""; + for (const char* fm : FILE_MANAGERS) { + if (!is_available(fm)) { + continue; + } + std::string cmd = std::string(fm) + " " + QUOTED + " >/dev/null 2>&1 &"; + if (std::system(cmd.c_str()) == 0) { + return; + } + } + + // Fallback: let the system choose a handler. + if (is_available("xdg-open")) { + std::string cmd = "xdg-open " + QUOTED + " >/dev/null 2>&1 &"; + if (std::system(cmd.c_str()) == 0) { + return; + } + } + + Logger::warn("Failed to open file manager for {}", path.string()); +#endif +} + +} // namespace Cubed::Tools \ No newline at end of file diff --git a/include/Cubed/tools/sensitive_filter.hpp b/include/Cubed/tools/sensitive_filter.hpp index bf93e9f..a0144c5 100644 --- a/include/Cubed/tools/sensitive_filter.hpp +++ b/include/Cubed/tools/sensitive_filter.hpp @@ -1,8 +1,10 @@ #pragma once - #include +#include +#include #include #include + namespace Cubed { class SensitiveFilter { public: diff --git a/include/Cubed/tools/time_tools.hpp b/include/Cubed/tools/time_tools.hpp index 3c22df6..aa1d767 100644 --- a/include/Cubed/tools/time_tools.hpp +++ b/include/Cubed/tools/time_tools.hpp @@ -1,11 +1,23 @@ #pragma once #include +#include #include +#include +#include namespace Cubed { namespace Tools { // return ms inline uint64_t get_time_ticks() { return SDL_GetTicks(); } + +inline std::string get_time_date_str() { + static auto* zone = std::chrono::current_zone(); + auto now = std::chrono::system_clock::now(); + auto local = + std::chrono::time_point_cast(zone->to_local(now)); + return std::format("{:%Y-%m-%d_%H-%M-%S}", local); +} + } // namespace Tools } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/button.hpp b/include/Cubed/ui/button.hpp index 1d8b306..a1f40f3 100644 --- a/include/Cubed/ui/button.hpp +++ b/include/Cubed/ui/button.hpp @@ -28,6 +28,7 @@ public: Button& set_background_image(const std::string& path, TextureManager& texture_manager); Button& set_default_image(TextureManager& texture_manager); + Button& set_texture(const Texture* texture, bool change_button_size); virtual Button& set_text(const std::string& text); Button& set_auto_scale(bool auto_scale); diff --git a/include/Cubed/ui/credits_ui.hpp b/include/Cubed/ui/credits_ui.hpp index ed9b7dc..9872c94 100644 --- a/include/Cubed/ui/credits_ui.hpp +++ b/include/Cubed/ui/credits_ui.hpp @@ -9,9 +9,11 @@ class CreditsUI : public UIManager { public: CreditsUI(CreditsScene& m_scene); void init() override; + void update_layout(int width, int height); private: bool handle_key_event(const KeyEvent& e) override; + bool handle_window_resize_event(const WindowResizeEvent& e) override; CreditsScene& m_scene; }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/host_game_ui.hpp b/include/Cubed/ui/host_game_ui.hpp index cdcd1b4..1a50b68 100644 --- a/include/Cubed/ui/host_game_ui.hpp +++ b/include/Cubed/ui/host_game_ui.hpp @@ -13,7 +13,7 @@ public: private: HostGameScene& m_scene; - Label* m_error_label; + Label* m_error_label = nullptr; void set_error(std::string_view error); void clear_error(); }; diff --git a/include/Cubed/ui/item_slot.hpp b/include/Cubed/ui/item_slot.hpp index 36bf015..df93f4c 100644 --- a/include/Cubed/ui/item_slot.hpp +++ b/include/Cubed/ui/item_slot.hpp @@ -26,7 +26,7 @@ private: void on_update(float dt) override; std::unique_ptr m_background; std::unique_ptr m_foreground; - ItemID m_id; + ItemID m_id = 0; float m_scale = 1.0f; bool m_hovered = false; }; diff --git a/include/Cubed/ui/join_game_ui.hpp b/include/Cubed/ui/join_game_ui.hpp index 1d15ab9..4cfa7ed 100644 --- a/include/Cubed/ui/join_game_ui.hpp +++ b/include/Cubed/ui/join_game_ui.hpp @@ -18,7 +18,7 @@ public: private: JoinGameScene& m_scene; - Label* m_error_label; + Label* m_error_label = nullptr; void set_error(std::string_view error); void clear_error(); }; diff --git a/include/Cubed/ui/screenshot_ui.hpp b/include/Cubed/ui/screenshot_ui.hpp new file mode 100644 index 0000000..28603ae --- /dev/null +++ b/include/Cubed/ui/screenshot_ui.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "Cubed/ui/image.hpp" +#include "Cubed/ui/ui_manager.hpp" + +#include +namespace Cubed { +class Texture; +class ScreenshotScene; +class ScreenshotUI : public UIManager { +public: + ScreenshotUI(ScreenshotScene& m_scene); + ~ScreenshotUI(); + + ScreenshotUI(const ScreenshotUI&) = delete; + ScreenshotUI(ScreenshotUI&&) = delete; + ScreenshotUI& operator=(const ScreenshotUI&) = delete; + ScreenshotUI& operator=(ScreenshotUI&&) = delete; + + void init() override; + + void update_layout(int width, int height); + + void open_lightbox(const Texture* image); + void update(float dt) override; + +private: + struct ImageInfo { + const Texture* texture = nullptr; + int width = 0; + int height = 0; + std::chrono::nanoseconds time{0}; + }; + + enum class SortType { NEWEST_FIRST, OLDEST_FIRST }; + + ScreenshotScene& m_scene; + std::vector m_image_infos; + SortType m_sort_type = SortType::NEWEST_FIRST; + bool m_need_rebuild = false; + Widget* m_lightbox = nullptr; + Image* m_lightbox_image = nullptr; + bool handle_window_resize_event(const WindowResizeEvent& e) override; + bool handle_key_event(const KeyEvent& e) override; + bool handle_mouse_button_event(const MouseButtonEvent& e) override; + bool handle_mouse_move_event(const MouseMoveEvent& e) override; + bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/scroll_view.hpp b/include/Cubed/ui/scroll_view.hpp new file mode 100644 index 0000000..7d99b83 --- /dev/null +++ b/include/Cubed/ui/scroll_view.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Cubed/ui/widget.hpp" +namespace Cubed { +class ColumnLayout; +class ScrollView : public Widget { +public: + ScrollView(const ScrollView&) = delete; + ScrollView(ScrollView&&) = delete; + ScrollView& operator=(const ScrollView&) = delete; + ScrollView& operator=(ScrollView&&) = delete; + + ScrollView(Widget* parent); + ~ScrollView(); + + void render(Renderer& renderer) override; + + float width() const override; + // visable height, you need notice offset + float height() const override; + + ScrollView& set_child(std::unique_ptr children); + + bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; + bool handle_mouse_move_event(const MouseMoveEvent& e) override; + Widget& set_fill_parent(bool fill) override; + Widget& set_fill_width(bool fill) override; + Widget& set_fill_height(bool fill) override; + +private: + ColumnLayout* m_child = nullptr; + bool m_hovered = false; + float m_scroll_speed = 50.0f; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/widget.hpp b/include/Cubed/ui/widget.hpp index 9b7928b..7b77b32 100644 --- a/include/Cubed/ui/widget.hpp +++ b/include/Cubed/ui/widget.hpp @@ -28,12 +28,19 @@ public: Widget& operator=(Widget&&) = delete; Widget(Widget* parent); + + static void set_window_size(int width, int height); + static int get_window_height(); + static int get_window_width(); + virtual ~Widget() = default; virtual void update(float dt); virtual void render(Renderer& renderer); virtual Widget& set_anchor(Anchor anchor); - virtual Widget& set_offset(glm::ivec2 offset); - static void set_window_size(int width, int height); + virtual Widget& set_offset(glm::vec2 offset); + virtual Widget& add_offset(glm::vec2 offset); + virtual glm::vec2 get_offset() const; + virtual Widget& set_visible(bool visible); // Returns the final display size @@ -56,6 +63,8 @@ public: virtual bool handle_window_resize_event(const WindowResizeEvent& e); virtual bool handle_mouse_move_event(const MouseMoveEvent& e); virtual bool handle_text_input_event(const TextInputEvent& e); + + virtual bool is_visible() const; void set_order(TraversalOrder order); template T& add_child(Args&&... args) { @@ -78,7 +87,7 @@ protected: bool m_fill_width = false; bool m_show_border = false; int m_border_size = 5; - glm::ivec2 m_offset{0, 0}; + glm::vec2 m_offset{0, 0}; std::vector>& children(); @@ -101,7 +110,7 @@ private: */ std::array, 4> m_border; TraversalOrder m_order = TraversalOrder::BACK_TO_FRONT; - glm::vec2 m_mouse_pos; + glm::vec2 m_mouse_pos{0.0f}; float m_width = 0.0f; float m_height = 0.0f; }; diff --git a/include/Cubed/window.hpp b/include/Cubed/window.hpp index 8be876a..380283b 100644 --- a/include/Cubed/window.hpp +++ b/include/Cubed/window.hpp @@ -50,8 +50,8 @@ private: bool m_imgui_init = false; bool m_game_running = false; bool m_enable_exclusive = false; - SDL_Window* m_window; - SDL_GLContext m_context; + SDL_Window* m_window = nullptr; + SDL_GLContext m_context = nullptr; int m_window_width = 0; int m_window_height = 0; SDL_DisplayID m_windowed_display = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2665999..b1fdc73 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,4 +106,7 @@ target_sources(${PROJECT_NAME} tools/json_utils.cpp gameplay/item_manager.cpp gameplay/block_manager.cpp + ui/screenshot_ui.cpp + scene/screenshot_scene.cpp + ui/scroll_view.cpp ) \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index eb5422f..78b787b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -187,7 +187,7 @@ void App::handle_argument(int argc, char** argv) { }}, {"--enable-filelog", [&](ArgParser&) { m_argument.enable_filelog = true; }}, - {"--enale-consolelog", + {"--enable-consolelog", [&](ArgParser&) { m_argument.enable_consolelog = true; }}, {"--direct-enter", [&](ArgParser&) { m_argument.direct_enter = true; }} diff --git a/src/audio/audio_engine.cpp b/src/audio/audio_engine.cpp index 1ea2e4b..02b43b9 100644 --- a/src/audio/audio_engine.cpp +++ b/src/audio/audio_engine.cpp @@ -119,6 +119,10 @@ void AudioEngine::init() { Logger::info("Audio Engine Init Success"); + Logger::info("OpenAL vendor: {}", alGetString(AL_VENDOR)); + Logger::info("OpenAL version: {}", alGetString(AL_VERSION)); + Logger::info("OpenAL renderer: {}", alGetString(AL_RENDERER)); + m_init = true; } @@ -148,11 +152,12 @@ void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos, return; } auto* source = m_pool->acquire(); - source->set_volume(m_sfx_volume); + if (!source) { Logger::error("Source is Full"); + return; } - + source->set_volume(m_sfx_volume); try { auto& buffer = m_sounds.get_buffer(sound); if (m_efx_supported && m_underwater) { @@ -176,6 +181,7 @@ void AudioEngine::play_2d(const std::string& sound, bool check) { auto* source = m_pool->acquire(); if (!source) { Logger::error("Source is Full"); + return; } source->set_volume(m_sfx_volume); @@ -301,8 +307,9 @@ void AudioEngine::receive_voice(std::span opus, return; } m_voice_source->set_pos(pos); - m_voice_source->push_pcm(std::span(pcm.data(), len), - AudioRecording::SAMPLE_RATE); + m_voice_source->push_pcm( + std::span(pcm.data(), static_cast(len)), + AudioRecording::SAMPLE_RATE); } AudioRecording& AudioEngine::audio_recording() { return m_recording; } diff --git a/src/audio/audio_source.cpp b/src/audio/audio_source.cpp index 9bb83b8..42ab83a 100644 --- a/src/audio/audio_source.cpp +++ b/src/audio/audio_source.cpp @@ -80,11 +80,9 @@ void AudioSource::set_pitch(float pitch) { } void AudioSource::play() { - - if (state() != AudioState::INITIAL) { - stop(); - } alSourcePlay(m_source); + + check_al_error(); } void AudioSource::play_2d(const AudioBuffer& buffer) { diff --git a/src/audio/source_pool.cpp b/src/audio/source_pool.cpp index 08f2707..aeffa57 100644 --- a/src/audio/source_pool.cpp +++ b/src/audio/source_pool.cpp @@ -1,5 +1,7 @@ #include "Cubed/audio/source_pool.hpp" +#include "Cubed/tools/log.hpp" + namespace Cubed { SourcePool::SourcePool(size_t size) { m_sources.reserve(size); @@ -10,8 +12,13 @@ SourcePool::SourcePool(size_t size) { SourcePool::~SourcePool() { m_sources.clear(); } void SourcePool::update() { for (auto& source_ptr : m_sources) { - if (source_ptr->in_use() && - source_ptr->state() == AudioState::STOPPED) { + if (!source_ptr->in_use()) { + continue; + } + + auto state = source_ptr->state(); + + if (state == AudioState::STOPPED || state == AudioState::INITIAL) { source_ptr->reset(); } } diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index aa09c40..ee7220c 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -109,6 +109,7 @@ ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) { unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed); m_random.init(seed); m_chunk_seed = seed; + m_neighbor_biome.fill(NONE); } void ChunkGenerator::init() { diff --git a/src/gameplay/client_entity_manager.cpp b/src/gameplay/client_entity_manager.cpp index 1af7e37..9f7f2fd 100644 --- a/src/gameplay/client_entity_manager.cpp +++ b/src/gameplay/client_entity_manager.cpp @@ -110,7 +110,8 @@ void ClientEntityManager::handle_entity_create(EntityID id, ASSERT(m_factories.contains(name)); m_factories[name](id); acc a; - ASSERT(m_entities.find(a, id)); + bool found = m_entities.find(a, id); + ASSERT(found); auto* c = m_registry.try_get(a->second); ASSERT(c); c->transform.position.value = pos; diff --git a/src/render/model_manager.cpp b/src/render/model_manager.cpp index f11931a..9a81b26 100644 --- a/src/render/model_manager.cpp +++ b/src/render/model_manager.cpp @@ -88,7 +88,7 @@ ModelManager::Handle ModelManager::load_model(std::string_view model_name) { auto model = m_loader.load(path); fs::path anim_path = path; anim_path = anim_path.parent_path() / "animation.json"; - load_anim_config(model, anim_path); + load_anim_config(model, anim_path.string()); ModelMap::accessor acc; if (m_models.insert(acc, m_next++)) { acc->second = std::move(model); diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index b6d974c..1e02858 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -9,10 +9,13 @@ #include "Cubed/tools/font.hpp" #include "Cubed/tools/log.hpp" #include "Cubed/tools/shader_tools.hpp" +#include "Cubed/tools/time_tools.hpp" +#include #include #include - +#include +namespace fs = std::filesystem; namespace Cubed { Renderer::Renderer(TextureManager& texture_manager, Config& config) @@ -139,7 +142,7 @@ void Renderer::begin_frame() { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void Renderer::end_frame() {} +void Renderer::end_frame() { handle_screenshot(); } void Renderer::begin_render_ui() { glEnable(GL_BLEND); @@ -288,6 +291,34 @@ bool Renderer::handle_frame_buffer_resize_event( return false; } +void Renderer::handle_screenshot() { + if (!m_save_screenshot) { + return; + } + m_save_screenshot = false; + std::array screen_stats; + glGetIntegerv(GL_VIEWPORT, screen_stats.data()); + int width = screen_stats[2]; + int height = screen_stats[3]; + std::vector pixels(width * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + std::vector flipped_pixels(width * height * 4); + for (int y = 0; y < height; ++y) { + std::memcpy(&flipped_pixels[y * width * 4], + &pixels[(height - 1 - y) * width * 4], width * 4); + } + fs::path path = SCREENSHOT_PATH; + fs::create_directories(path); + fs::path image = + path / std::format("screenshot {}.png", Tools::get_time_date_str()); + if (!stbi_write_png(image.string().c_str(), width, height, 4, + flipped_pixels.data(), width * 4)) { + Logger::error("Failed to write {} image", image.string()); + } else { + Logger::info("save {} success!", image.string()); + } +} + float& Renderer::ambient_strength() { return m_world_renderer.ambient_strength(); } @@ -341,4 +372,5 @@ float Renderer::frame_width() const { return m_frame_width; } const glm::mat4& Renderer::p_mat() const { return m_world_proj_matrix; } const std::vector& Renderer::vao() const { return m_vao; } ModelRender& Renderer::model_renderer() { return m_model_renderer; } +void Renderer::save_screenshot() { m_save_screenshot = true; } } // namespace Cubed \ No newline at end of file diff --git a/src/scene/scene_manager.cpp b/src/scene/scene_manager.cpp index 6c58b16..2ac27f2 100644 --- a/src/scene/scene_manager.cpp +++ b/src/scene/scene_manager.cpp @@ -4,6 +4,7 @@ #include "Cubed/scene/host_game_scene.hpp" #include "Cubed/scene/join_game_scene.hpp" #include "Cubed/scene/main_menu_scene.hpp" +#include "Cubed/scene/screenshot_scene.hpp" #include "Cubed/scene/settings_scene.hpp" #include "Cubed/scene/world_scene.hpp" @@ -33,15 +34,24 @@ bool SceneManager::handle_event(const Event& e) { } void SceneManager::request_change(SceneType type) { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::CHANGE, type}; } void SceneManager::request_push(SceneType type) { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::PUSH, type}; } void SceneManager::request_pop() { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::POP, std::nullopt}; } @@ -103,6 +113,8 @@ std::unique_ptr SceneManager::create_scene(SceneType type) { return std::make_unique(*this); case SceneType::JOIN_GAME: return std::make_unique(*this); + case SceneType::SCREENSHOT: + return std::make_unique(*this); } std::string err = std::format("Unknown Scene"); diff --git a/src/scene/screenshot_scene.cpp b/src/scene/screenshot_scene.cpp new file mode 100644 index 0000000..1e04ff9 --- /dev/null +++ b/src/scene/screenshot_scene.cpp @@ -0,0 +1,20 @@ +#include "Cubed/scene/screenshot_scene.hpp" + +namespace Cubed { +ScreenshotScene::ScreenshotScene(SceneManager& scene_manager) + : m_scene_manager(scene_manager), m_ui(*this) {} + +ScreenshotScene::~ScreenshotScene() {} + +void ScreenshotScene::update(float dt) { m_ui.update(dt); } +void ScreenshotScene::render(Renderer& renderer) { m_ui.render(renderer); } +bool ScreenshotScene::handle_event(const Event& e) { + return m_ui.handle_event(e); +} +void ScreenshotScene::on_enter() { m_ui.init(); } +void ScreenshotScene::on_leave() {} +void ScreenshotScene::on_re_enter() {} + +SceneManager& ScreenshotScene::scene_manager() { return m_scene_manager; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index 6e5b0e1..d4c6a05 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -3,6 +3,7 @@ #include "Cubed/app.hpp" #include "Cubed/render/renderer.hpp" #include "Cubed/scene/scene_manager.hpp" +#include "Cubed/tools/time_tools.hpp" namespace Cubed { WorldScene::WorldScene(SceneManager& scene_manager) : m_scene_manager(scene_manager), m_dev_panel(*this), @@ -380,7 +381,16 @@ bool WorldScene::handle_key_event(const KeyEvent& e) { } } } - + if (e.key == Key::F2 && e.action == KeyAction::PRESS) { + m_scene_manager.app().renderer().save_screenshot(); + ChatMessage msg; + msg.system_msg = true; + msg.color = Color::WHITE; + msg.text = "Saved Screenshot!"; + msg.time = Tools::get_time_ticks(); + m_hud_ui.add_chat_message(msg); + return true; + } if (m_paused) { switch (m_paused_ui) { case PauseUI::INVENTORY: diff --git a/src/texture_manager.cpp b/src/texture_manager.cpp index c49d7a8..4698542 100644 --- a/src/texture_manager.cpp +++ b/src/texture_manager.cpp @@ -57,12 +57,14 @@ const Texture* TextureManager::get_texture_array() const { const Texture* TextureManager::get_cross_plane_array() const { return m_cross_plane_array.get(); } -const Texture* TextureManager::get_image_texture(const std::string& path) { +const Texture* TextureManager::get_image_texture(const std::string& path, + bool full_path, + bool check_exist) { auto it = m_ui_map.find(path); if (it != m_ui_map.end()) { return it->second.get(); } - return load_image_texture(path); + return load_image_texture(path, full_path, check_exist); } const Texture* TextureManager::get_pbr_texture() const { @@ -151,9 +153,15 @@ void TextureManager::load_cross_plane_texture(unsigned id) { CROSS_PLANE_SIZE, CROSS_PLANE_SIZE); } -const Texture* TextureManager::load_image_texture(const std::string& path) { +const Texture* TextureManager::load_image_texture(const std::string& path, + bool full_path, + bool check_exist) { - auto image_data = (Tools::load_image_data(path)); + auto image_data = (Tools::load_image_data(path, check_exist, full_path)); + if (!image_data.data) { + Logger::error("Failed to load image texture: {}", path); + return nullptr; + } std::unique_ptr image = std::make_unique(TextureType::TEXTURE_2D); image->tex_image_2d(RGBA, RGBA, GL_UNSIGNED_BYTE, image_data.data, diff --git a/src/tools/shader_tools.cpp b/src/tools/shader_tools.cpp index e24f1aa..38b9589 100644 --- a/src/tools/shader_tools.cpp +++ b/src/tools/shader_tools.cpp @@ -184,7 +184,7 @@ ImageData load_image_data(const std::string& tex_image_path, bool check_exist, path = ASSETS_PATH + tex_image_path; } if (check_exist) { - ASSERT_MSG(fs::is_regular_file(path), path.c_str()); + ASSERT_MSG(fs::is_regular_file(path), path.string().c_str()); } unsigned char* data = nullptr; int width, height, channels; diff --git a/src/ui/button.cpp b/src/ui/button.cpp index 6b8091a..72aa13e 100644 --- a/src/ui/button.cpp +++ b/src/ui/button.cpp @@ -103,7 +103,18 @@ Button& Button::set_background_image(const std::string& path, Button& Button::set_default_image(TextureManager& texture_manager) { return set_background_image(DEFAULT_BUTTON_IMAGE, texture_manager); } - +Button& Button::set_texture(const Texture* texture, bool change_button_size) { + if (!texture) { + Logger::error("Button Background Image is nullptr"); + return *this; + } + m_background->set_texture(texture, false); + if (change_button_size) { + set_width(texture->width()); + set_height(texture->height()); + } + return *this; +} Button& Button::set_text(const std::string& text) { m_foreground->set_text(text); update_text_scale(); diff --git a/src/ui/credits_ui.cpp b/src/ui/credits_ui.cpp index 003c8cc..d2bdfb8 100644 --- a/src/ui/credits_ui.cpp +++ b/src/ui/credits_ui.cpp @@ -8,12 +8,19 @@ #include "Cubed/ui/column_layout.hpp" #include "Cubed/ui/image.hpp" #include "Cubed/ui/rect.hpp" +#include "Cubed/ui/scroll_view.hpp" +#include namespace Cubed { CreditsUI::CreditsUI(CreditsScene& scene) : m_scene(scene) {} void CreditsUI::init() { + auto& renderer = m_scene.scene_manager().app().renderer(); + update_layout(renderer.window_width(), renderer.window_height()); +} +void CreditsUI::update_layout(int, int height) { + m_root_widget.reset(); auto image = std::make_unique(nullptr); image ->set_image("texture/ui/background.png", @@ -23,25 +30,38 @@ void CreditsUI::init() { auto& rect = image->add_child(); rect.set_color(Color::BLACK).set_alpha(0.7f).set_fill_parent(true); + auto& title = rect.add_child