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()) {
274 sel_min, sel_max, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.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);
364 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
372 cursor_screen.y + pixel_size / 2);
377 ImVec2 pixel_min = cursor_screen;
378 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
380 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
387 int half = brush / 2;
390 ImVec2 brush_min(cursor_screen.x - half * pixel_size,
391 cursor_screen.y - half * pixel_size);
392 ImVec2 brush_max(cursor_screen.x + (brush - half) * pixel_size,
393 cursor_screen.y + (brush - half) * pixel_size);
397 ? IM_COL32(255, 0, 0, 50)
398 : IM_COL32(0, 255, 0, 50);
403 ? IM_COL32(255, 100, 100, 200)
404 : IM_COL32(100, 255, 100, 200);
410 if (cursor_x_ < 0 || cursor_x_ >= sheet.
width() ||
cursor_y_ < 0 ||
416 auto palette = sheet.
palette();
418 ImGui::BeginTooltip();
421 ImGui::Text(
"Index: %d", color_index);
423 if (color_index < palette.size()) {
424 ImGui::Text(
"SNES: $%04X", palette[color_index].snes());
425 ImVec4 color(palette[color_index].rgb().x / 255.0f,
426 palette[color_index].rgb().y / 255.0f,
427 palette[color_index].rgb().z / 255.0f, 1.0f);
428 ImGui::ColorButton(
"##ColorPreview", color, ImGuiColorEditFlags_NoTooltip,
430 if (color_index == 0) {
432 ImGui::TextDisabled(
"(Transparent)");
439 ImGui::Text(
"Colors");
442 ImGui::TextDisabled(
"No sheet");
448 auto palette = sheet.palette();
451 for (
int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
452 if (i > 0 && i % 4 == 0) {
458 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
459 palette[i].rgb().z / 255.0f, 1.0f);
463 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
464 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
467 std::string
id = absl::StrFormat(
"##Color%d", i);
468 if (ImGui::ColorButton(
470 ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoBorder,
477 ImGui::PopStyleColor();
478 ImGui::PopStyleVar();
481 if (ImGui::IsItemHovered()) {
482 ImGui::BeginTooltip();
483 ImGui::Text(
"Index: %d", i);
484 ImGui::Text(
"SNES: $%04X", palette[i].snes());
485 ImGui::Text(
"RGB: %d, %d, %d",
static_cast<int>(palette[i].rgb().x),
486 static_cast<int>(palette[i].rgb().y),
487 static_cast<int>(palette[i].rgb().z));
489 ImGui::Text(
"(Transparent)");
498 ImGui::Text(
"Current:");
500 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
506 ImGui::Text(
"Navigator");
509 ImGui::TextDisabled(
"No sheet");
515 if (!sheet.texture())
519 float mini_scale = 0.5f;
520 float mini_width = sheet.width() * mini_scale;
521 float mini_height = sheet.height() * mini_scale;
523 ImVec2 pos = ImGui::GetCursorScreenPos();
525 ImGui::GetWindowDrawList()->AddImage(
526 (ImTextureID)(intptr_t)sheet.texture(), pos,
527 ImVec2(pos.x + mini_width, pos.y + mini_height));
532 ImGui::Dummy(ImVec2(mini_width, mini_height));
550 ImGui::Text(
"Tile: %d, %d", tile_x, tile_y);
560 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f),
"(Modified)");
565 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
570 if (!ImGui::IsItemHovered()) {
576 ImVec2 mouse_pos = ImGui::GetMousePos();
579 cursor_x_ =
static_cast<int>(pixel_pos.x);
580 cursor_y_ =
static_cast<int>(pixel_pos.y);
590 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
628 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
is_drawing_) {
653 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
is_drawing_) {
681 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
694 for (
int dy = -half; dy < size - half; dy++) {
695 for (
int dx = -half; dx < size - half; dx++) {
698 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
714 for (
int dy = -half; dy < size - half; dy++) {
715 for (
int dx = -half; dx < size - half; dx++) {
718 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
719 sheet.WriteToPixel(px, py, 0);
732 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
735 uint8_t target_color = sheet.GetPixel(x, y);
738 if (target_color == fill_color)
742 std::queue<std::pair<int, int>> queue;
743 std::vector<bool> visited(sheet.width() * sheet.height(),
false);
746 visited[y * sheet.width() + x] =
true;
748 while (!queue.empty()) {
749 auto [cx, cy] = queue.front();
752 sheet.WriteToPixel(cx, cy, fill_color);
755 const int dx[] = {0, 0, -1, 1};
756 const int dy[] = {-1, 1, 0, 0};
758 for (
int i = 0; i < 4; i++) {
762 if (nx >= 0 && nx < sheet.width() && ny >= 0 && ny < sheet.height()) {
763 int idx = ny * sheet.width() + nx;
764 if (!visited[idx] && sheet.GetPixel(nx, ny) == target_color) {
766 queue.push({nx, ny});
779 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
783 auto palette = sheet.palette();
787 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
788 color.rgb().z / 255.0f, 1.0f);
798 int dx = std::abs(x2 - x1);
799 int dy = std::abs(y2 - y1);
800 int sx = x1 < x2 ? 1 : -1;
801 int sy = y1 < y2 ? 1 : -1;
805 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
809 if (x1 == x2 && y1 == y2)
832 int min_x = std::min(x1, x2);
833 int max_x = std::max(x1, x2);
834 int min_y = std::min(y1, y2);
835 int max_y = std::max(y1, y2);
838 for (
int y = min_y; y <= max_y; y++) {
839 for (
int x = min_x; x <= max_x; x++) {
840 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
847 for (
int x = min_x; x <= max_x; x++) {
848 if (x >= 0 && x < sheet.width()) {
849 if (min_y >= 0 && min_y < sheet.height())
851 if (max_y >= 0 && max_y < sheet.height())
856 for (
int y = min_y; y <= max_y; y++) {
857 if (y >= 0 && y < sheet.height()) {
858 if (min_x >= 0 && min_x < sheet.width())
860 if (max_x >= 0 && max_x < sheet.width())
902 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
903 src_y < sheet.height()) {
905 sheet.GetPixel(src_x, src_y);
931 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
932 dest_y < sheet.height()) {
935 sheet.WriteToPixel(dest_x, dest_y, pixel);
985 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