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.
This commit is contained in:
2026-08-07 18:09:30 +08:00
parent f397143e30
commit 5f60910aeb
6 changed files with 134 additions and 29 deletions

View File

@@ -28,6 +28,7 @@ public:
Button& set_background_image(const std::string& path, Button& set_background_image(const std::string& path,
TextureManager& texture_manager); TextureManager& texture_manager);
Button& set_default_image(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); virtual Button& set_text(const std::string& text);
Button& set_auto_scale(bool auto_scale); Button& set_auto_scale(bool auto_scale);

View File

@@ -1,7 +1,9 @@
#pragma once #pragma once
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/ui_manager.hpp" #include "Cubed/ui/ui_manager.hpp"
namespace Cubed { namespace Cubed {
class Texture;
class ScreenshotScene; class ScreenshotScene;
class ScreenshotUI : public UIManager { class ScreenshotUI : public UIManager {
public: public:
@@ -17,9 +19,23 @@ public:
void update_layout(int width, int height); void update_layout(int width, int height);
void open_lightbox(const Texture* image);
private: private:
bool handle_window_resize_event(const WindowResizeEvent& e) override; struct ImageInfo {
const Texture* texture = nullptr;
int width = 0;
int height = 0;
};
ScreenshotScene& m_scene; ScreenshotScene& m_scene;
std::vector<ImageInfo> m_image_infos;
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 } // namespace Cubed

View File

@@ -28,6 +28,11 @@ public:
Widget& operator=(Widget&&) = delete; Widget& operator=(Widget&&) = delete;
Widget(Widget* parent); 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 ~Widget() = default;
virtual void update(float dt); virtual void update(float dt);
virtual void render(Renderer& renderer); virtual void render(Renderer& renderer);
@@ -35,7 +40,7 @@ public:
virtual Widget& set_offset(glm::vec2 offset); virtual Widget& set_offset(glm::vec2 offset);
virtual Widget& add_offset(glm::vec2 offset); virtual Widget& add_offset(glm::vec2 offset);
virtual glm::vec2 get_offset() const; virtual glm::vec2 get_offset() const;
static void set_window_size(int width, int height);
virtual Widget& set_visible(bool visible); virtual Widget& set_visible(bool visible);
// Returns the final display size // Returns the final display size
@@ -58,6 +63,8 @@ public:
virtual bool handle_window_resize_event(const WindowResizeEvent& e); virtual bool handle_window_resize_event(const WindowResizeEvent& e);
virtual bool handle_mouse_move_event(const MouseMoveEvent& e); virtual bool handle_mouse_move_event(const MouseMoveEvent& e);
virtual bool handle_text_input_event(const TextInputEvent& e); virtual bool handle_text_input_event(const TextInputEvent& e);
virtual bool is_visible() const;
void set_order(TraversalOrder order); void set_order(TraversalOrder order);
template <typename T, typename... Args> T& add_child(Args&&... args) { template <typename T, typename... Args> T& add_child(Args&&... args) {

View File

@@ -103,7 +103,18 @@ Button& Button::set_background_image(const std::string& path,
Button& Button::set_default_image(TextureManager& texture_manager) { Button& Button::set_default_image(TextureManager& texture_manager) {
return set_background_image(DEFAULT_BUTTON_IMAGE, 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) { Button& Button::set_text(const std::string& text) {
m_foreground->set_text(text); m_foreground->set_text(text);
update_text_scale(); update_text_scale();

View File

@@ -27,6 +27,9 @@ void ScreenshotUI::init() {
update_layout(renderer.window_width(), renderer.window_height()); update_layout(renderer.window_width(), renderer.window_height());
} }
void ScreenshotUI::update_layout(int width, int height) { void ScreenshotUI::update_layout(int width, int height) {
m_image_infos.clear();
auto& texture_manager = m_scene.scene_manager().app().texture_manager(); auto& texture_manager = m_scene.scene_manager().app().texture_manager();
m_root_widget.reset(); m_root_widget.reset();
auto bg = std::make_unique<Rect>(nullptr); auto bg = std::make_unique<Rect>(nullptr);
@@ -37,25 +40,11 @@ void ScreenshotUI::update_layout(int width, int height) {
title.set_text(tr("menu.screenshot")).set_anchor(Anchor::TOP_CENTER); title.set_text(tr("menu.screenshot")).set_anchor(Anchor::TOP_CENTER);
title.set_offset({0, 5}); title.set_offset({0, 5});
struct ImageInfo {
const Texture* texture = nullptr;
int width = 0;
int height = 0;
};
auto& return_button = bg->add_child<Button>();
return_button.set_default_image(texture_manager);
return_button.set_anchor(Anchor::BOTTOM_CENTER);
return_button.set_offset({0, -30});
return_button.set_text(tr("button.return"));
return_button.set_clicked(
[this]() { m_scene.scene_manager().request_pop(); });
fs::path path = Renderer::SCREENSHOT_PATH; fs::path path = Renderer::SCREENSHOT_PATH;
std::vector<ImageInfo> image_infos;
constexpr float IMAGE_SCALE = 0.25; constexpr float IMAGE_SCALE = 0.25;
ScrollView* sv = nullptr;
if (fs::exists(path)) { if (fs::exists(path)) {
for (auto& entry : fs::recursive_directory_iterator(path)) { for (auto& entry : fs::recursive_directory_iterator(path)) {
if (!fs::is_regular_file(entry)) { if (!fs::is_regular_file(entry)) {
@@ -65,12 +54,11 @@ void ScreenshotUI::update_layout(int width, int height) {
continue; continue;
} }
ImageInfo info; ImageInfo info;
Logger::info("Path: {}", entry.path().string());
info.texture = info.texture =
texture_manager.get_image_texture(entry.path().string(), true); texture_manager.get_image_texture(entry.path().string(), true);
info.height = info.texture->height() * IMAGE_SCALE; info.height = info.texture->height() * IMAGE_SCALE;
info.width = info.texture->width() * IMAGE_SCALE; info.width = info.texture->width() * IMAGE_SCALE;
image_infos.emplace_back(std::move(info)); m_image_infos.emplace_back(std::move(info));
} }
constexpr int PADDING = 50; constexpr int PADDING = 50;
constexpr int SPACING = 10; constexpr int SPACING = 10;
@@ -81,14 +69,11 @@ void ScreenshotUI::update_layout(int width, int height) {
auto& scroll = bg->add_child<ScrollView>(); auto& scroll = bg->add_child<ScrollView>();
sv = &scroll;
scroll.set_anchor(Anchor::TOP_CENTER) scroll.set_anchor(Anchor::TOP_CENTER)
.set_offset({0, 10 + title.height()}); .set_offset({0, 10 + title.height()});
float scroll_height = height - scroll.get_offset().y +
return_button.get_offset().y -
return_button.height();
scroll.set_height(std::max(0.0f, scroll_height));
auto root_column = std::make_unique<ColumnLayout>(&scroll); auto root_column = std::make_unique<ColumnLayout>(&scroll);
root_column->set_child_anchor(ColumnLayoutAnchor::LEFT) root_column->set_child_anchor(ColumnLayoutAnchor::LEFT)
.set_spacing(10) .set_spacing(10)
@@ -100,7 +85,7 @@ void ScreenshotUI::update_layout(int width, int height) {
row->set_spacing(SPACING); row->set_spacing(SPACING);
}; };
for (auto& image : image_infos) { for (auto& image : m_image_infos) {
if (!row) { if (!row) {
create_row(); create_row();
} }
@@ -110,24 +95,105 @@ void ScreenshotUI::update_layout(int width, int height) {
create_row(); create_row();
} }
cur_width += image.width; cur_width += image.width;
auto& im = row->add_child<Image>(); auto& button = row->add_child<Button>();
im.set_texture(image.texture, true).set_scale(IMAGE_SCALE); button.set_texture(image.texture, true).set_scale(IMAGE_SCALE);
button.set_clicked(
[image, this]() { open_lightbox(image.texture); });
} }
scroll.set_child(std::move(root_column)); scroll.set_child(std::move(root_column));
} }
if (!fs::exists(path) || image_infos.empty()) { if (!fs::exists(path) || m_image_infos.empty()) {
auto& label = bg->add_child<Label>(); auto& label = bg->add_child<Label>();
label.set_text(tr("screenshot.no_screenshot")) label.set_text(tr("screenshot.no_screenshot"))
.set_color(Color::WHITE) .set_color(Color::WHITE)
.set_anchor(Anchor::CENTER); .set_anchor(Anchor::CENTER);
} }
auto& return_button = bg->add_child<Button>();
return_button.set_default_image(texture_manager);
return_button.set_anchor(Anchor::BOTTOM_CENTER);
return_button.set_offset({0, -30});
return_button.set_text(tr("button.return"));
return_button.set_clicked(
[this]() { m_scene.scene_manager().request_pop(); });
if (sv) {
float scroll_height = height - sv->get_offset().y +
return_button.get_offset().y -
return_button.height();
sv->set_height(std::max(0.0f, scroll_height));
}
auto& rect = bg->add_child<Rect>();
rect.set_fill_parent(true);
rect.set_color(Color::BLACK).set_alpha(0.5f);
m_lightbox = &rect;
auto& lightbox_image = rect.add_child<Image>();
m_lightbox_image = &lightbox_image;
m_lightbox->set_visible(false);
m_root_widget = std::move(bg); m_root_widget = std::move(bg);
} }
void ScreenshotUI::open_lightbox(const Texture* image) {
if (image) {
constexpr float PADDING = 50.0f;
m_lightbox_image->set_texture(image, true);
float target_height =
std::max(50.0f, Widget::get_window_height() - PADDING * 2);
float target_width =
std::max(50.0f, Widget::get_window_width() - PADDING * 2);
float image_width = image->width();
float image_height = image->height();
float scale_w = target_width / image_width;
float scale_h = target_height / image_height;
float scale = std::min(scale_h, scale_w);
m_lightbox_image->set_scale(scale);
m_lightbox_image->set_anchor(Anchor::CENTER);
}
m_lightbox->set_visible(true);
}
bool ScreenshotUI::handle_window_resize_event(const WindowResizeEvent& e) { bool ScreenshotUI::handle_window_resize_event(const WindowResizeEvent& e) {
update_layout(e.width, e.height); update_layout(e.width, e.height);
return UIManager::handle_window_resize_event(e); return UIManager::handle_window_resize_event(e);
} }
bool ScreenshotUI::handle_key_event(const KeyEvent& e) {
if (UIManager::handle_key_event(e)) {
return true;
}
if (e.key == Key::ESCAPE && e.action == KeyAction::PRESS) {
if (m_lightbox && m_lightbox->is_visible()) {
m_lightbox->set_visible(false);
return true;
}
m_scene.scene_manager().request_pop();
return true;
}
return false;
}
bool ScreenshotUI::handle_mouse_button_event(const MouseButtonEvent& e) {
if (m_lightbox && m_lightbox->is_visible()) {
if (e.key == MouseKey::LEFT_BUTTON && e.action == KeyAction::PRESS) {
m_lightbox->set_visible(false);
}
return true;
}
return UIManager::handle_mouse_button_event(e);
}
bool ScreenshotUI::handle_mouse_move_event(const MouseMoveEvent& e) {
if (m_lightbox && m_lightbox->is_visible()) {
return true;
}
return UIManager::handle_mouse_move_event(e);
}
bool ScreenshotUI::handle_mouse_wheel_event(const MouseWheelEvent& e) {
if (m_lightbox && m_lightbox->is_visible()) {
return true;
}
return UIManager::handle_mouse_wheel_event(e);
}
} // namespace Cubed } // namespace Cubed

View File

@@ -174,6 +174,9 @@ void Widget::set_window_size(int width, int height) {
return; return;
} }
int Widget::get_window_height() { return m_window_height; }
int Widget::get_window_width() { return m_window_width; }
Widget& Widget::set_visible(bool visible) { Widget& Widget::set_visible(bool visible) {
m_visible = visible; m_visible = visible;
return *this; return *this;
@@ -335,4 +338,5 @@ bool Widget::handle_text_input_event(const TextInputEvent& e) {
return false; return false;
} }
void Widget::set_order(TraversalOrder order) { m_order = order; } void Widget::set_order(TraversalOrder order) { m_order = order; }
bool Widget::is_visible() const { return m_visible; }
} // namespace Cubed } // namespace Cubed