refactor: Enhance OverworldEditor with Dynamic Context Menu and Tile Selector Widget
- Replaced static context menu setup in OverworldEditor with dynamic configuration based on the current map state, improving usability and responsiveness. - Introduced TileSelectorWidget for better tile selection management, allowing for a more intuitive user experience when selecting tiles. - Updated canvas controls to include zoom in and zoom out functionalities, enhancing the editor's navigation capabilities. - Cleaned up legacy context menu code and improved overall organization for better maintainability and clarity.
This commit is contained in:
190
src/app/gui/widgets/tile_selector_widget.cc
Normal file
190
src/app/gui/widgets/tile_selector_widget.cc
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "app/gui/widgets/tile_selector_widget.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace yaze::gui {
|
||||
|
||||
TileSelectorWidget::TileSelectorWidget(std::string widget_id)
|
||||
: config_(), total_tiles_(config_.total_tiles), widget_id_(std::move(widget_id)) {}
|
||||
|
||||
TileSelectorWidget::TileSelectorWidget(std::string widget_id, Config config)
|
||||
: config_(config), total_tiles_(config.total_tiles), widget_id_(std::move(widget_id)) {}
|
||||
|
||||
void TileSelectorWidget::AttachCanvas(Canvas* canvas) { canvas_ = canvas; }
|
||||
|
||||
void TileSelectorWidget::SetTileCount(int total_tiles) {
|
||||
total_tiles_ = std::max(total_tiles, 0);
|
||||
if (!IsValidTileId(selected_tile_id_)) {
|
||||
selected_tile_id_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TileSelectorWidget::SetSelectedTile(int tile_id) {
|
||||
if (IsValidTileId(tile_id)) {
|
||||
selected_tile_id_ = tile_id;
|
||||
}
|
||||
}
|
||||
|
||||
TileSelectorWidget::RenderResult TileSelectorWidget::Render(gfx::Bitmap& atlas,
|
||||
bool atlas_ready) {
|
||||
RenderResult result;
|
||||
|
||||
if (!canvas_) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const int tile_display_size =
|
||||
static_cast<int>(config_.tile_size * config_.display_scale);
|
||||
|
||||
// Calculate total content size for ImGui child window scrolling
|
||||
const int num_rows = (total_tiles_ + config_.tiles_per_row - 1) / config_.tiles_per_row;
|
||||
const ImVec2 content_size(
|
||||
config_.tiles_per_row * tile_display_size + config_.draw_offset.x * 2,
|
||||
num_rows * tile_display_size + config_.draw_offset.y * 2
|
||||
);
|
||||
|
||||
// Set content size for ImGui child window (must be called before DrawBackground)
|
||||
ImGui::SetCursorPos(ImVec2(0, 0));
|
||||
ImGui::Dummy(content_size);
|
||||
ImGui::SetCursorPos(ImVec2(0, 0));
|
||||
|
||||
// Handle pending scroll (deferred from ScrollToTile call outside render context)
|
||||
if (pending_scroll_tile_id_ >= 0) {
|
||||
if (IsValidTileId(pending_scroll_tile_id_)) {
|
||||
const ImVec2 target = TileOrigin(pending_scroll_tile_id_);
|
||||
if (pending_scroll_use_imgui_) {
|
||||
const ImVec2 window_size = ImGui::GetWindowSize();
|
||||
float scroll_x = target.x - (window_size.x / 2.0f) + (tile_display_size / 2.0f);
|
||||
float scroll_y = target.y - (window_size.y / 2.0f) + (tile_display_size / 2.0f);
|
||||
scroll_x = std::max(0.0f, scroll_x);
|
||||
scroll_y = std::max(0.0f, scroll_y);
|
||||
ImGui::SetScrollX(scroll_x);
|
||||
ImGui::SetScrollY(scroll_y);
|
||||
}
|
||||
}
|
||||
pending_scroll_tile_id_ = -1; // Clear pending scroll
|
||||
}
|
||||
|
||||
canvas_->DrawBackground();
|
||||
canvas_->DrawContextMenu();
|
||||
|
||||
if (atlas_ready && atlas.is_active()) {
|
||||
canvas_->DrawBitmap(atlas, static_cast<int>(config_.draw_offset.x),
|
||||
static_cast<int>(config_.draw_offset.y),
|
||||
config_.display_scale);
|
||||
|
||||
result = HandleInteraction(tile_display_size);
|
||||
|
||||
if (config_.show_tile_ids) {
|
||||
DrawTileIdLabels(tile_display_size);
|
||||
}
|
||||
|
||||
DrawHighlight(tile_display_size);
|
||||
}
|
||||
|
||||
canvas_->DrawGrid();
|
||||
canvas_->DrawOverlay();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TileSelectorWidget::RenderResult TileSelectorWidget::HandleInteraction(
|
||||
int tile_display_size) {
|
||||
RenderResult result;
|
||||
|
||||
if (!ImGui::IsItemHovered()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const bool clicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
const bool double_clicked =
|
||||
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
|
||||
|
||||
if (clicked || double_clicked) {
|
||||
const int hovered_tile = ResolveTileAtCursor(tile_display_size);
|
||||
if (IsValidTileId(hovered_tile)) {
|
||||
result.tile_clicked = clicked;
|
||||
result.tile_double_clicked = double_clicked;
|
||||
if (hovered_tile != selected_tile_id_) {
|
||||
selected_tile_id_ = hovered_tile;
|
||||
result.selection_changed = true;
|
||||
}
|
||||
result.selected_tile = selected_tile_id_;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int TileSelectorWidget::ResolveTileAtCursor(int tile_display_size) const {
|
||||
if (!canvas_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const ImVec2 screen_pos = ImGui::GetIO().MousePos;
|
||||
const ImVec2 origin = canvas_->zero_point();
|
||||
const ImVec2 scroll = canvas_->scrolling();
|
||||
|
||||
// Convert screen position to canvas content position (accounting for scroll)
|
||||
ImVec2 local = ImVec2(screen_pos.x - origin.x - config_.draw_offset.x - scroll.x,
|
||||
screen_pos.y - origin.y - config_.draw_offset.y - scroll.y);
|
||||
|
||||
if (local.x < 0.0f || local.y < 0.0f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int column = static_cast<int>(local.x / tile_display_size);
|
||||
const int row = static_cast<int>(local.y / tile_display_size);
|
||||
|
||||
return row * config_.tiles_per_row + column;
|
||||
}
|
||||
|
||||
void TileSelectorWidget::DrawHighlight(int tile_display_size) const {
|
||||
if (!canvas_ || !IsValidTileId(selected_tile_id_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int column = selected_tile_id_ % config_.tiles_per_row;
|
||||
const int row = selected_tile_id_ / config_.tiles_per_row;
|
||||
|
||||
const float x = config_.draw_offset.x + column * tile_display_size;
|
||||
const float y = config_.draw_offset.y + row * tile_display_size;
|
||||
|
||||
canvas_->DrawOutlineWithColor(static_cast<int>(x), static_cast<int>(y),
|
||||
tile_display_size, tile_display_size,
|
||||
config_.highlight_color);
|
||||
}
|
||||
|
||||
void TileSelectorWidget::DrawTileIdLabels(int) const {
|
||||
// Future enhancement: draw ImGui text overlay with tile indices.
|
||||
}
|
||||
|
||||
void TileSelectorWidget::ScrollToTile(int tile_id, bool use_imgui_scroll) {
|
||||
if (!canvas_ || !IsValidTileId(tile_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer scroll until next render (when we're in the correct ImGui window context)
|
||||
pending_scroll_tile_id_ = tile_id;
|
||||
pending_scroll_use_imgui_ = use_imgui_scroll;
|
||||
}
|
||||
|
||||
ImVec2 TileSelectorWidget::TileOrigin(int tile_id) const {
|
||||
if (!IsValidTileId(tile_id)) {
|
||||
return ImVec2(-1, -1);
|
||||
}
|
||||
const int tile_display_size =
|
||||
static_cast<int>(config_.tile_size * config_.display_scale);
|
||||
const int column = tile_id % config_.tiles_per_row;
|
||||
const int row = tile_id / config_.tiles_per_row;
|
||||
return ImVec2(config_.draw_offset.x + column * tile_display_size,
|
||||
config_.draw_offset.y + row * tile_display_size);
|
||||
}
|
||||
|
||||
bool TileSelectorWidget::IsValidTileId(int tile_id) const {
|
||||
return tile_id >= 0 && tile_id < total_tiles_;
|
||||
}
|
||||
|
||||
} // namespace yaze::gui
|
||||
|
||||
|
||||
71
src/app/gui/widgets/tile_selector_widget.h
Normal file
71
src/app/gui/widgets/tile_selector_widget.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef YAZE_APP_GUI_WIDGETS_TILE_SELECTOR_WIDGET_H
|
||||
#define YAZE_APP_GUI_WIDGETS_TILE_SELECTOR_WIDGET_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze::gui {
|
||||
|
||||
/**
|
||||
* @brief Reusable tile selector built on top of Canvas.
|
||||
*
|
||||
* Minimal mutable state, designed for reuse across editors and automation.
|
||||
*/
|
||||
class TileSelectorWidget {
|
||||
public:
|
||||
struct Config {
|
||||
int tile_size = 16;
|
||||
float display_scale = 2.0f;
|
||||
int tiles_per_row = 8;
|
||||
int total_tiles = 512;
|
||||
ImVec2 draw_offset = {2.0f, 0.0f};
|
||||
bool show_tile_ids = false;
|
||||
ImVec4 highlight_color = {1.0f, 0.85f, 0.35f, 1.0f};
|
||||
};
|
||||
|
||||
struct RenderResult {
|
||||
bool tile_clicked = false;
|
||||
bool tile_double_clicked = false;
|
||||
bool selection_changed = false;
|
||||
int selected_tile = -1;
|
||||
};
|
||||
|
||||
explicit TileSelectorWidget(std::string widget_id);
|
||||
TileSelectorWidget(std::string widget_id, Config config);
|
||||
|
||||
void AttachCanvas(Canvas* canvas);
|
||||
void SetTileCount(int total_tiles);
|
||||
void SetSelectedTile(int tile_id);
|
||||
int GetSelectedTileID() const { return selected_tile_id_; }
|
||||
|
||||
RenderResult Render(gfx::Bitmap& atlas, bool atlas_ready);
|
||||
|
||||
void ScrollToTile(int tile_id, bool use_imgui_scroll = true);
|
||||
ImVec2 TileOrigin(int tile_id) const;
|
||||
|
||||
private:
|
||||
RenderResult HandleInteraction(int tile_display_size);
|
||||
int ResolveTileAtCursor(int tile_display_size) const;
|
||||
void DrawHighlight(int tile_display_size) const;
|
||||
void DrawTileIdLabels(int tile_display_size) const;
|
||||
bool IsValidTileId(int tile_id) const;
|
||||
|
||||
Canvas* canvas_ = nullptr;
|
||||
Config config_{};
|
||||
int selected_tile_id_ = 0;
|
||||
int total_tiles_ = 0;
|
||||
std::string widget_id_;
|
||||
|
||||
// Deferred scroll state (for when ScrollToTile is called outside render context)
|
||||
mutable int pending_scroll_tile_id_ = -1;
|
||||
mutable bool pending_scroll_use_imgui_ = true;
|
||||
};
|
||||
|
||||
} // namespace yaze::gui
|
||||
|
||||
#endif // YAZE_APP_GUI_WIDGETS_TILE_SELECTOR_WIDGET_H
|
||||
|
||||
|
||||
Reference in New Issue
Block a user