6#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
29 ImGui::BeginChild(
"##PixelEditorContent", ImVec2(0, -24),
false);
32 ImGui::BeginChild(
"##ColorPickerSide", ImVec2(120, 0),
true);
41 ImGui::BeginChild(
"##CanvasArea", ImVec2(0, 0),
true,
42 ImGuiWindowFlags_HorizontalScrollbar);
61 ImGui::BeginChild(
"##PixelEditorContent", ImVec2(0, -24),
false);
64 ImGui::BeginChild(
"##ColorPickerSide", ImVec2(200, 0),
true);
73 ImGui::BeginChild(
"##CanvasArea", ImVec2(0, 0),
true,
74 ImGuiWindowFlags_HorizontalScrollbar);
83 return absl::OkStatus();
88 auto tool_button = [
this](
PixelTool tool,
const char* icon,
89 const char* tooltip) {
92 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
94 if (ImGui::Button(icon)) {
98 ImGui::PopStyleColor();
100 if (ImGui::IsItemHovered()) {
101 ImGui::SetTooltip(
"%s", tooltip);
123 ImGui::SetNextItemWidth(80);
125 if (ImGui::SliderInt(
"##BrushSize", &brush, 1, 8,
"%d px")) {
147 ImGui::EndDisabled();
163 ImGui::EndDisabled();
175 ImGui::SetNextItemWidth(100);
177 if (ImGui::SliderFloat(
"##Zoom", &zoom, 1.0f, 16.0f,
"%.0fx")) {
210 ImGui::TextDisabled(
"No sheet selected. Select a sheet from the browser.");
215 if (ImGui::BeginTabBar(
"##SheetTabs",
216 ImGuiTabBarFlags_AutoSelectNewTabs |
217 ImGuiTabBarFlags_Reorderable |
218 ImGuiTabBarFlags_TabListPopupButton)) {
219 std::vector<uint16_t> sheets_to_close;
223 std::string tab_label = absl::StrFormat(
"%02X", sheet_id);
228 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
235 if (!sheet.is_active()) {
236 ImGui::TextDisabled(
"Sheet %02X is not active", sheet_id);
254 if (sheet.texture()) {
256 (ImTextureID)(intptr_t)sheet.texture(),
274 IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
278 IM_COL32(0, 0, 0, 128), 0.0f, 0, 1.0f);
323 sheets_to_close.push_back(sheet_id);
328 for (uint16_t sheet_id : sheets_to_close) {
337 float canvas_height) {
338 const float cell_size = 8.0f;
339 const ImU32 color1 = IM_COL32(180, 180, 180, 255);
340 const ImU32 color2 = IM_COL32(220, 220, 220, 255);
343 int cols =
static_cast<int>(canvas_width / cell_size) + 1;
344 int rows =
static_cast<int>(canvas_height / cell_size) + 1;
346 for (
int row = 0; row < rows; row++) {
347 for (
int col = 0; col < cols; col++) {
348 bool is_light = (row + col) % 2 == 0;
349 ImVec2 p_min(origin.x + col * cell_size, origin.y + row * cell_size);
350 ImVec2 p_max(std::min(p_min.x + cell_size, origin.x + canvas_width),
351 std::min(p_min.y + cell_size, origin.y + canvas_height));
353 is_light ? color1 : color2);
363 ImVec2 v_start(cursor_screen.x + pixel_size / 2,
365 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
372 cursor_screen.y + pixel_size / 2);
374 cursor_screen.y + pixel_size / 2);
379 ImVec2 pixel_min = cursor_screen;
380 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
382 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
389 int half = brush / 2;
392 ImVec2 brush_min(cursor_screen.x - half * pixel_size,
393 cursor_screen.y - half * pixel_size);
394 ImVec2 brush_max(cursor_screen.x + (brush - half) * pixel_size,
395 cursor_screen.y + (brush - half) * pixel_size);
399 ? IM_COL32(255, 0, 0, 50)
400 : IM_COL32(0, 255, 0, 50);
405 ? IM_COL32(255, 100, 100, 200)
406 : IM_COL32(100, 255, 100, 200);
412 if (cursor_x_ < 0 || cursor_x_ >= sheet.
width() ||
cursor_y_ < 0 ||
418 auto palette = sheet.
palette();
420 ImGui::BeginTooltip();
423 ImGui::Text(
"Index: %d", color_index);
425 if (color_index < palette.size()) {
426 ImGui::Text(
"SNES: $%04X", palette[color_index].snes());
427 ImVec4 color(palette[color_index].rgb().x / 255.0f,
428 palette[color_index].rgb().y / 255.0f,
429 palette[color_index].rgb().z / 255.0f, 1.0f);
430 ImGui::ColorButton(
"##ColorPreview", color, ImGuiColorEditFlags_NoTooltip,
432 if (color_index == 0) {
434 ImGui::TextDisabled(
"(Transparent)");
441 ImGui::Text(
"Colors");
444 ImGui::TextDisabled(
"No sheet");
450 auto palette = sheet.palette();
453 for (
int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
454 if (i > 0 && i % 4 == 0) {
460 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
461 palette[i].rgb().z / 255.0f, 1.0f);
465 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
466 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
469 std::string
id = absl::StrFormat(
"##Color%d", i);
470 if (ImGui::ColorButton(
id.c_str(), color,
471 ImGuiColorEditFlags_NoTooltip |
472 ImGuiColorEditFlags_NoBorder,
479 ImGui::PopStyleColor();
480 ImGui::PopStyleVar();
483 if (ImGui::IsItemHovered()) {
484 ImGui::BeginTooltip();
485 ImGui::Text(
"Index: %d", i);
486 ImGui::Text(
"SNES: $%04X", palette[i].snes());
487 ImGui::Text(
"RGB: %d, %d, %d",
static_cast<int>(palette[i].rgb().x),
488 static_cast<int>(palette[i].rgb().y),
489 static_cast<int>(palette[i].rgb().z));
491 ImGui::Text(
"(Transparent)");
500 ImGui::Text(
"Current:");
502 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
508 ImGui::Text(
"Navigator");
511 ImGui::TextDisabled(
"No sheet");
517 if (!sheet.texture())
return;
520 float mini_scale = 0.5f;
521 float mini_width = sheet.width() * mini_scale;
522 float mini_height = sheet.height() * mini_scale;
524 ImVec2 pos = ImGui::GetCursorScreenPos();
526 ImGui::GetWindowDrawList()->AddImage((ImTextureID)(intptr_t)sheet.texture(),
528 ImVec2(pos.x + mini_width, pos.y + mini_height));
533 ImGui::Dummy(ImVec2(mini_width, mini_height));
551 ImGui::Text(
"Tile: %d, %d", tile_x, tile_y);
561 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f),
"(Modified)");
566 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
571 if (!ImGui::IsItemHovered()) {
577 ImVec2 mouse_pos = ImGui::GetMousePos();
580 cursor_x_ =
static_cast<int>(pixel_pos.x);
581 cursor_y_ =
static_cast<int>(pixel_pos.y);
591 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
629 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
is_drawing_) {
654 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
is_drawing_) {
682 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
695 for (
int dy = -half; dy < size - half; dy++) {
696 for (
int dx = -half; dx < size - half; dx++) {
699 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
715 for (
int dy = -half; dy < size - half; dy++) {
716 for (
int dx = -half; dx < size - half; dx++) {
719 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
720 sheet.WriteToPixel(px, py, 0);
733 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
return;
735 uint8_t target_color = sheet.GetPixel(x, y);
738 if (target_color == fill_color)
return;
741 std::queue<std::pair<int, int>> queue;
742 std::vector<bool> visited(sheet.width() * sheet.height(),
false);
745 visited[y * sheet.width() + x] =
true;
747 while (!queue.empty()) {
748 auto [cx, cy] = queue.front();
751 sheet.WriteToPixel(cx, cy, fill_color);
754 const int dx[] = {0, 0, -1, 1};
755 const int dy[] = {-1, 1, 0, 0};
757 for (
int i = 0; i < 4; i++) {
761 if (nx >= 0 && nx < sheet.width() && ny >= 0 && ny < sheet.height()) {
762 int idx = ny * sheet.width() + nx;
763 if (!visited[idx] && sheet.GetPixel(nx, ny) == target_color) {
765 queue.push({nx, ny});
778 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
782 auto palette = sheet.palette();
786 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
787 color.rgb().z / 255.0f, 1.0f);
797 int dx = std::abs(x2 - x1);
798 int dy = std::abs(y2 - y1);
799 int sx = x1 < x2 ? 1 : -1;
800 int sy = y1 < y2 ? 1 : -1;
804 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
808 if (x1 == x2 && y1 == y2)
break;
830 int min_x = std::min(x1, x2);
831 int max_x = std::max(x1, x2);
832 int min_y = std::min(y1, y2);
833 int max_y = std::max(y1, y2);
836 for (
int y = min_y; y <= max_y; y++) {
837 for (
int x = min_x; x <= max_x; x++) {
838 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
845 for (
int x = min_x; x <= max_x; x++) {
846 if (x >= 0 && x < sheet.width()) {
847 if (min_y >= 0 && min_y < sheet.height())
849 if (max_y >= 0 && max_y < sheet.height())
854 for (
int y = min_y; y <= max_y; y++) {
855 if (y >= 0 && y < sheet.height()) {
856 if (min_x >= 0 && min_x < sheet.width())
858 if (max_x >= 0 && max_x < sheet.width())
900 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
901 src_y < sheet.height()) {
903 sheet.GetPixel(src_x, src_y);
928 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
929 dest_y < sheet.height()) {
932 sheet.WriteToPixel(dest_x, dest_y, pixel);
979 return ImVec2(px, py);
bool PopRedoState(PixelEditorSnapshot &out)
Pop and return the last redo state.
void MarkSheetModified(uint16_t sheet_id)
Mark a sheet as modified for save tracking.
void PushUndoState(uint16_t sheet_id, const std::vector< uint8_t > &pixel_data, const gfx::SnesPalette &palette)
Push current state to undo stack before modification.
uint8_t current_color_index
bool PopUndoState(PixelEditorSnapshot &out)
Pop and return the last undo state.
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.
void SetTool(PixelTool tool)
Set the current editing tool.
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 (EditorPanel 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.
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.
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 DrawCanvas()
Draw the main editing canvas.
void SaveUndoState()
Save current state for undo.
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.
Snapshot for undo/redo operations.
std::vector< uint8_t > pixel_data
std::vector< uint8_t > pixel_data