9#include "absl/strings/str_format.h"
18#include "imgui/imgui.h"
40 constexpr float kColorPickerWidth = 200.0f;
41 constexpr float kStatusBarHeight = 24.0f;
44 ImGui::BeginChild(
"##PixelEditorContent", ImVec2(0, -kStatusBarHeight),
48 ImGui::BeginChild(
"##ColorPickerSide", ImVec2(kColorPickerWidth, 0),
true);
57 ImGui::BeginChild(
"##CanvasArea", ImVec2(0, 0),
true,
58 ImGuiWindowFlags_HorizontalScrollbar);
67 return absl::OkStatus();
72 auto tool_button = [
this](
PixelTool tool,
const char* icon,
73 const char* tooltip) {
75 std::optional<gui::StyleColorGuard> sel_guard;
79 if (ImGui::Button(icon)) {
83 if (ImGui::IsItemHovered()) {
84 ImGui::SetTooltip(
"%s", tooltip);
106 ImGui::SetNextItemWidth(80);
108 if (ImGui::SliderInt(
"##BrushSize", &brush, 1, 8,
"%d px")) {
123 ImGui::EndDisabled();
131 ImGui::EndDisabled();
141 ImGui::SetNextItemWidth(100);
143 if (ImGui::SliderFloat(
"##Zoom", &zoom, 1.0f, 16.0f,
"%.0fx")) {
176 tr(
"No sheet selected. Select a sheet from the browser."));
182 ImGuiTabBarFlags_AutoSelectNewTabs |
183 ImGuiTabBarFlags_Reorderable |
184 ImGuiTabBarFlags_TabListPopupButton)) {
185 std::vector<uint16_t> sheets_to_close;
189 std::string tab_label = absl::StrFormat(
"%02X", sheet_id);
194 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
201 if (!sheet.is_active()) {
202 ImGui::TextDisabled(tr(
"Sheet %02X is not active"), sheet_id);
220 if (sheet.texture()) {
243 sel_min, sel_max, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
292 sheets_to_close.push_back(sheet_id);
297 for (uint16_t sheet_id : sheets_to_close) {
306 float canvas_height) {
307 const float cell_size = 8.0f;
308 const ImU32 color1 = IM_COL32(180, 180, 180, 255);
309 const ImU32 color2 = IM_COL32(220, 220, 220, 255);
312 int cols =
static_cast<int>(canvas_width / cell_size) + 1;
313 int rows =
static_cast<int>(canvas_height / cell_size) + 1;
315 for (
int row = 0; row < rows; row++) {
316 for (
int col = 0; col < cols; col++) {
317 bool is_light = (row + col) % 2 == 0;
318 ImVec2 p_min(origin.x + col * cell_size, origin.y + row * cell_size);
319 ImVec2 p_max(std::min(p_min.x + cell_size, origin.x + canvas_width),
320 std::min(p_min.y + cell_size, origin.y + canvas_height));
322 is_light ? color1 : color2);
333 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
341 cursor_screen.y + pixel_size / 2);
346 ImVec2 pixel_min = cursor_screen;
347 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
349 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
356 int half = brush / 2;
359 ImVec2 brush_min(cursor_screen.x - half * pixel_size,
360 cursor_screen.y - half * pixel_size);
361 ImVec2 brush_max(cursor_screen.x + (brush - half) * pixel_size,
362 cursor_screen.y + (brush - half) * pixel_size);
366 ? IM_COL32(255, 0, 0, 50)
367 : IM_COL32(0, 255, 0, 50);
372 ? IM_COL32(255, 100, 100, 200)
373 : IM_COL32(100, 255, 100, 200);
379 if (cursor_x_ < 0 || cursor_x_ >= sheet.
width() ||
cursor_y_ < 0 ||
385 auto palette = sheet.
palette();
387 ImGui::BeginTooltip();
390 ImGui::Text(tr(
"Index: %d"), color_index);
392 if (color_index < palette.size()) {
393 ImGui::Text(tr(
"SNES: $%04X"), palette[color_index].snes());
394 ImVec4 color(palette[color_index].rgb().x / 255.0f,
395 palette[color_index].rgb().y / 255.0f,
396 palette[color_index].rgb().z / 255.0f, 1.0f);
397 ImGui::ColorButton(
"##ColorPreview", color, ImGuiColorEditFlags_NoTooltip,
399 if (color_index == 0) {
401 ImGui::TextDisabled(tr(
"(Transparent)"));
415 const double now = ImGui::GetTime();
422 const int tiles_per_row = sheet.
width() / 8;
423 if (tiles_per_row <= 0) {
427 const int tile_x =
static_cast<int>(tile_index % tiles_per_row);
428 const int tile_y =
static_cast<int>(tile_index / tiles_per_row);
431 0.5f + 0.5f * std::sin(
static_cast<float>(elapsed) * 6.0f);
432 const float alpha = 0.25f + (pulse * 0.35f);
439 const ImU32 fill_color = ImGui::GetColorU32(sel);
441 sel_outline.w = 0.9f;
442 const ImU32 outline_color = ImGui::GetColorU32(sel_outline);
446 if (ImGui::IsMouseHoveringRect(min, max)) {
447 ImGui::BeginTooltip();
448 ImGui::Text(tr(
"Focus tile: %d (sheet %02X)"), tile_index,
458 ImGui::Text(tr(
"Colors"));
461 ImGui::TextDisabled(tr(
"No sheet"));
467 auto palette = sheet.palette();
470 for (
int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
471 if (i > 0 && i % 4 == 0) {
477 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
478 palette[i].rgb().z / 255.0f, 1.0f);
481 std::optional<gui::StyleVarGuard> border_var;
482 std::optional<gui::StyleColorGuard> border_color;
484 border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
488 std::string
id = absl::StrFormat(
"##Color%d", i);
489 if (ImGui::ColorButton(
491 ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoBorder,
498 border_color.reset();
502 if (ImGui::IsItemHovered()) {
503 ImGui::BeginTooltip();
504 ImGui::Text(tr(
"Index: %d"), i);
505 ImGui::Text(tr(
"SNES: $%04X"), palette[i].snes());
506 ImGui::Text(tr(
"RGB: %d, %d, %d"),
static_cast<int>(palette[i].rgb().x),
507 static_cast<int>(palette[i].rgb().y),
508 static_cast<int>(palette[i].rgb().z));
510 ImGui::Text(tr(
"(Transparent)"));
519 ImGui::Text(tr(
"Current:"));
521 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
527 ImGui::Text(tr(
"Navigator"));
530 ImGui::TextDisabled(tr(
"No sheet"));
536 if (!sheet.texture())
540 float mini_scale = 0.5f;
541 float mini_width = sheet.width() * mini_scale;
542 float mini_height = sheet.height() * mini_scale;
544 ImVec2 pos = ImGui::GetCursorScreenPos();
546 ImGui::GetWindowDrawList()->AddImage(
547 (ImTextureID)(intptr_t)sheet.texture(), pos,
548 ImVec2(pos.x + mini_width, pos.y + mini_height));
553 ImGui::Dummy(ImVec2(mini_width, mini_height));
571 ImGui::Text(tr(
"Tile: %d, %d"), tile_x, tile_y);
593 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
598 if (!ImGui::IsItemHovered()) {
604 ImVec2 mouse_pos = ImGui::GetMousePos();
607 cursor_x_ =
static_cast<int>(pixel_pos.x);
608 cursor_y_ =
static_cast<int>(pixel_pos.y);
618 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
656 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
is_drawing_) {
681 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
is_drawing_) {
712 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
725 for (
int dy = -half; dy < size - half; dy++) {
726 for (
int dx = -half; dx < size - half; dx++) {
729 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
745 for (
int dy = -half; dy < size - half; dy++) {
746 for (
int dx = -half; dx < size - half; dx++) {
749 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
750 sheet.WriteToPixel(px, py, 0);
763 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
766 uint8_t target_color = sheet.GetPixel(x, y);
769 if (target_color == fill_color)
773 std::queue<std::pair<int, int>> queue;
774 std::vector<bool> visited(sheet.width() * sheet.height(),
false);
777 visited[y * sheet.width() + x] =
true;
779 while (!queue.empty()) {
780 auto [cx, cy] = queue.front();
783 sheet.WriteToPixel(cx, cy, fill_color);
786 const int dx[] = {0, 0, -1, 1};
787 const int dy[] = {-1, 1, 0, 0};
789 for (
int i = 0; i < 4; i++) {
793 if (nx >= 0 && nx < sheet.width() && ny >= 0 && ny < sheet.height()) {
794 int idx = ny * sheet.width() + nx;
795 if (!visited[idx] && sheet.GetPixel(nx, ny) == target_color) {
797 queue.push({nx, ny});
810 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
814 auto palette = sheet.palette();
818 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
819 color.rgb().z / 255.0f, 1.0f);
829 int dx = std::abs(x2 - x1);
830 int dy = std::abs(y2 - y1);
831 int sx = x1 < x2 ? 1 : -1;
832 int sy = y1 < y2 ? 1 : -1;
836 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
840 if (x1 == x2 && y1 == y2)
863 int min_x = std::min(x1, x2);
864 int max_x = std::max(x1, x2);
865 int min_y = std::min(y1, y2);
866 int max_y = std::max(y1, y2);
869 for (
int y = min_y; y <= max_y; y++) {
870 for (
int x = min_x; x <= max_x; x++) {
871 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
878 for (
int x = min_x; x <= max_x; x++) {
879 if (x >= 0 && x < sheet.width()) {
880 if (min_y >= 0 && min_y < sheet.height())
882 if (max_y >= 0 && max_y < sheet.height())
887 for (
int y = min_y; y <= max_y; y++) {
888 if (y >= 0 && y < sheet.height()) {
889 if (min_x >= 0 && min_x < sheet.width())
891 if (max_x >= 0 && max_x < sheet.width())
933 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
934 src_y < sheet.height()) {
936 sheet.GetPixel(src_x, src_y);
962 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
963 dest_y < sheet.height()) {
966 sheet.WriteToPixel(dest_x, dest_y, pixel);
1024 auto after_data = sheet.vector();
1032 std::move(after_data), std::move(description)));
1042 return ImVec2(px, py);
void MarkSheetModified(uint16_t sheet_id)
Mark a sheet as modified for save tracking.
uint8_t current_color_index
bool show_pixel_info_tooltip
bool show_transparency_grid
void SetZoom(float zoom)
Set zoom level with clamping.
std::set< uint16_t > modified_sheets
bool show_cursor_crosshair
uint16_t current_sheet_id
std::set< uint16_t > open_sheets
const char * GetToolName() const
Get tool name for status display.
void CloseSheet(uint16_t sheet_id)
Close a sheet tab.
TileHighlight tile_highlight
void SetTool(PixelTool tool)
Set the current editing tool.
UndoManager * undo_manager_
void EndSelection()
Finalize the selection.
void DrawLine(int x1, int y1, int x2, int y2)
Draw line from start to end.
void DrawColorPicker()
Draw the color palette picker.
void ApplyEraser(int x, int y)
Apply eraser tool at position.
void DrawPixelInfoTooltip(const gfx::Bitmap &sheet)
Draw tooltip with pixel information.
void DrawCursorCrosshair()
Draw crosshair at cursor position.
void DrawMiniMap()
Draw the mini navigation map.
void Draw(bool *p_open) override
Draw the pixel editor UI (WindowContent interface)
void HandleCanvasInput()
Handle canvas mouse input for current tool.
ImVec2 ScreenToPixel(ImVec2 screen_pos)
Convert screen coordinates to pixel coordinates.
void DrawBrushPreview()
Draw brush size preview circle.
void ApplyEyedropper(int x, int y)
Apply eyedropper tool at position.
void CopySelection()
Copy selection to clipboard.
uint16_t pending_undo_sheet_id_
void UpdateSelection(int x, int y)
Update selection during drag.
void FlipSelectionVertical()
Flip selection vertically.
void ApplyBrush(int x, int y)
Apply brush tool at position.
void Initialize()
Initialize the panel.
void FlipSelectionHorizontal()
Flip selection horizontally.
void DrawViewControls()
Draw zoom and view controls.
void ApplyPencil(int x, int y)
Apply pencil tool at position.
void DrawToolbar()
Draw the toolbar with tool selection.
absl::Status Update()
Legacy Update method for backward compatibility.
ImVec2 PixelToScreen(int x, int y)
Convert pixel coordinates to screen coordinates.
void BeginSelection(int x, int y)
Start a new selection.
void DrawStatusBar()
Draw the status bar with cursor position.
void DrawTransparencyGrid(float canvas_width, float canvas_height)
Draw checkerboard pattern for transparent pixels.
void DrawTileHighlight(const gfx::Bitmap &sheet)
Draw a transient highlight for a target tile.
GraphicsEditorState * state_
void ApplyFill(int x, int y)
Apply flood fill starting at position.
void PasteSelection(int x, int y)
Paste clipboard at position.
void DrawRectangle(int x1, int y1, int x2, int y2, bool filled)
Draw rectangle from start to end.
void FinalizeUndoAction()
Finalize the current undo action by capturing the after-snapshot and pushing a GraphicsPixelEditActio...
void DrawCanvas()
Draw the main editing canvas.
std::vector< uint8_t > pending_undo_before_data_
void SaveUndoState()
Save current state for undo (captures before-snapshot)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
auto mutable_gfx_sheets()
Get mutable reference to all graphics sheets.
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Represents a bitmap image optimized for SNES ROM hacking.
const SnesPalette & palette() const
uint8_t GetPixel(int x, int y) const
Get the palette index at the given x,y coordinates.
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
#define ICON_MD_FORMAT_COLOR_FILL
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_CROP_SQUARE
#define ICON_MD_HORIZONTAL_RULE
#define ICON_MD_SELECT_ALL
#define HOVER_HINT(string)
PixelTool
Pixel editing tool types for the graphics editor.
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
ImVec4 GetSelectedColor()
ImVec4 GetModifiedColor()
bool ToolbarIconButton(const char *icon, const char *tooltip, bool is_active)
Convenience wrapper for toolbar-sized icon buttons.
std::vector< uint8_t > pixel_data