8#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
39 constexpr float kColorPickerWidth = 200.0f;
40 constexpr float kStatusBarHeight = 24.0f;
43 ImGui::BeginChild(
"##PixelEditorContent", ImVec2(0, -kStatusBarHeight),
47 ImGui::BeginChild(
"##ColorPickerSide", ImVec2(kColorPickerWidth, 0),
true);
56 ImGui::BeginChild(
"##CanvasArea", ImVec2(0, 0),
true,
57 ImGuiWindowFlags_HorizontalScrollbar);
66 return absl::OkStatus();
71 auto tool_button = [
this](
PixelTool tool,
const char* icon,
72 const char* tooltip) {
74 std::optional<gui::StyleColorGuard> sel_guard;
78 if (ImGui::Button(icon)) {
82 if (ImGui::IsItemHovered()) {
83 ImGui::SetTooltip(
"%s", tooltip);
105 ImGui::SetNextItemWidth(80);
107 if (ImGui::SliderInt(
"##BrushSize", &brush, 1, 8,
"%d px")) {
122 ImGui::EndDisabled();
130 ImGui::EndDisabled();
140 ImGui::SetNextItemWidth(100);
142 if (ImGui::SliderFloat(
"##Zoom", &zoom, 1.0f, 16.0f,
"%.0fx")) {
174 ImGui::TextDisabled(
"No sheet selected. Select a sheet from the browser.");
180 ImGuiTabBarFlags_AutoSelectNewTabs |
181 ImGuiTabBarFlags_Reorderable |
182 ImGuiTabBarFlags_TabListPopupButton)) {
183 std::vector<uint16_t> sheets_to_close;
187 std::string tab_label = absl::StrFormat(
"%02X", sheet_id);
192 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
199 if (!sheet.is_active()) {
200 ImGui::TextDisabled(
"Sheet %02X is not active", sheet_id);
218 if (sheet.texture()) {
241 sel_min, sel_max, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
290 sheets_to_close.push_back(sheet_id);
295 for (uint16_t sheet_id : sheets_to_close) {
304 float canvas_height) {
305 const float cell_size = 8.0f;
306 const ImU32 color1 = IM_COL32(180, 180, 180, 255);
307 const ImU32 color2 = IM_COL32(220, 220, 220, 255);
310 int cols =
static_cast<int>(canvas_width / cell_size) + 1;
311 int rows =
static_cast<int>(canvas_height / cell_size) + 1;
313 for (
int row = 0; row < rows; row++) {
314 for (
int col = 0; col < cols; col++) {
315 bool is_light = (row + col) % 2 == 0;
316 ImVec2 p_min(origin.x + col * cell_size, origin.y + row * cell_size);
317 ImVec2 p_max(std::min(p_min.x + cell_size, origin.x + canvas_width),
318 std::min(p_min.y + cell_size, origin.y + canvas_height));
320 is_light ? color1 : color2);
331 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
339 cursor_screen.y + pixel_size / 2);
344 ImVec2 pixel_min = cursor_screen;
345 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
347 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
354 int half = brush / 2;
357 ImVec2 brush_min(cursor_screen.x - half * pixel_size,
358 cursor_screen.y - half * pixel_size);
359 ImVec2 brush_max(cursor_screen.x + (brush - half) * pixel_size,
360 cursor_screen.y + (brush - half) * pixel_size);
364 ? IM_COL32(255, 0, 0, 50)
365 : IM_COL32(0, 255, 0, 50);
370 ? IM_COL32(255, 100, 100, 200)
371 : IM_COL32(100, 255, 100, 200);
377 if (cursor_x_ < 0 || cursor_x_ >= sheet.
width() ||
cursor_y_ < 0 ||
383 auto palette = sheet.
palette();
385 ImGui::BeginTooltip();
388 ImGui::Text(
"Index: %d", color_index);
390 if (color_index < palette.size()) {
391 ImGui::Text(
"SNES: $%04X", palette[color_index].snes());
392 ImVec4 color(palette[color_index].rgb().x / 255.0f,
393 palette[color_index].rgb().y / 255.0f,
394 palette[color_index].rgb().z / 255.0f, 1.0f);
395 ImGui::ColorButton(
"##ColorPreview", color, ImGuiColorEditFlags_NoTooltip,
397 if (color_index == 0) {
399 ImGui::TextDisabled(
"(Transparent)");
413 const double now = ImGui::GetTime();
420 const int tiles_per_row = sheet.
width() / 8;
421 if (tiles_per_row <= 0) {
425 const int tile_x =
static_cast<int>(tile_index % tiles_per_row);
426 const int tile_y =
static_cast<int>(tile_index / tiles_per_row);
429 0.5f + 0.5f * std::sin(
static_cast<float>(elapsed) * 6.0f);
430 const float alpha = 0.25f + (pulse * 0.35f);
437 const ImU32 fill_color = ImGui::GetColorU32(sel);
439 sel_outline.w = 0.9f;
440 const ImU32 outline_color = ImGui::GetColorU32(sel_outline);
444 if (ImGui::IsMouseHoveringRect(min, max)) {
445 ImGui::BeginTooltip();
446 ImGui::Text(
"Focus tile: %d (sheet %02X)", tile_index,
456 ImGui::Text(
"Colors");
459 ImGui::TextDisabled(
"No sheet");
465 auto palette = sheet.palette();
468 for (
int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
469 if (i > 0 && i % 4 == 0) {
475 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
476 palette[i].rgb().z / 255.0f, 1.0f);
479 std::optional<gui::StyleVarGuard> border_var;
480 std::optional<gui::StyleColorGuard> border_color;
482 border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
486 std::string
id = absl::StrFormat(
"##Color%d", i);
487 if (ImGui::ColorButton(
489 ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoBorder,
496 border_color.reset();
500 if (ImGui::IsItemHovered()) {
501 ImGui::BeginTooltip();
502 ImGui::Text(
"Index: %d", i);
503 ImGui::Text(
"SNES: $%04X", palette[i].snes());
504 ImGui::Text(
"RGB: %d, %d, %d",
static_cast<int>(palette[i].rgb().x),
505 static_cast<int>(palette[i].rgb().y),
506 static_cast<int>(palette[i].rgb().z));
508 ImGui::Text(
"(Transparent)");
517 ImGui::Text(
"Current:");
519 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
525 ImGui::Text(
"Navigator");
528 ImGui::TextDisabled(
"No sheet");
534 if (!sheet.texture())
538 float mini_scale = 0.5f;
539 float mini_width = sheet.width() * mini_scale;
540 float mini_height = sheet.height() * mini_scale;
542 ImVec2 pos = ImGui::GetCursorScreenPos();
544 ImGui::GetWindowDrawList()->AddImage(
545 (ImTextureID)(intptr_t)sheet.texture(), pos,
546 ImVec2(pos.x + mini_width, pos.y + mini_height));
551 ImGui::Dummy(ImVec2(mini_width, mini_height));
569 ImGui::Text(
"Tile: %d, %d", tile_x, tile_y);
591 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
596 if (!ImGui::IsItemHovered()) {
602 ImVec2 mouse_pos = ImGui::GetMousePos();
605 cursor_x_ =
static_cast<int>(pixel_pos.x);
606 cursor_y_ =
static_cast<int>(pixel_pos.y);
616 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
654 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
is_drawing_) {
679 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
is_drawing_) {
710 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
723 for (
int dy = -half; dy < size - half; dy++) {
724 for (
int dx = -half; dx < size - half; dx++) {
727 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
743 for (
int dy = -half; dy < size - half; dy++) {
744 for (
int dx = -half; dx < size - half; dx++) {
747 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
748 sheet.WriteToPixel(px, py, 0);
761 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
764 uint8_t target_color = sheet.GetPixel(x, y);
767 if (target_color == fill_color)
771 std::queue<std::pair<int, int>> queue;
772 std::vector<bool> visited(sheet.width() * sheet.height(),
false);
775 visited[y * sheet.width() + x] =
true;
777 while (!queue.empty()) {
778 auto [cx, cy] = queue.front();
781 sheet.WriteToPixel(cx, cy, fill_color);
784 const int dx[] = {0, 0, -1, 1};
785 const int dy[] = {-1, 1, 0, 0};
787 for (
int i = 0; i < 4; i++) {
791 if (nx >= 0 && nx < sheet.width() && ny >= 0 && ny < sheet.height()) {
792 int idx = ny * sheet.width() + nx;
793 if (!visited[idx] && sheet.GetPixel(nx, ny) == target_color) {
795 queue.push({nx, ny});
808 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
812 auto palette = sheet.palette();
816 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
817 color.rgb().z / 255.0f, 1.0f);
827 int dx = std::abs(x2 - x1);
828 int dy = std::abs(y2 - y1);
829 int sx = x1 < x2 ? 1 : -1;
830 int sy = y1 < y2 ? 1 : -1;
834 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
838 if (x1 == x2 && y1 == y2)
861 int min_x = std::min(x1, x2);
862 int max_x = std::max(x1, x2);
863 int min_y = std::min(y1, y2);
864 int max_y = std::max(y1, y2);
867 for (
int y = min_y; y <= max_y; y++) {
868 for (
int x = min_x; x <= max_x; x++) {
869 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
876 for (
int x = min_x; x <= max_x; x++) {
877 if (x >= 0 && x < sheet.width()) {
878 if (min_y >= 0 && min_y < sheet.height())
880 if (max_y >= 0 && max_y < sheet.height())
885 for (
int y = min_y; y <= max_y; y++) {
886 if (y >= 0 && y < sheet.height()) {
887 if (min_x >= 0 && min_x < sheet.width())
889 if (max_x >= 0 && max_x < sheet.width())
931 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
932 src_y < sheet.height()) {
934 sheet.GetPixel(src_x, src_y);
960 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
961 dest_y < sheet.height()) {
964 sheet.WriteToPixel(dest_x, dest_y, pixel);
1022 auto after_data = sheet.vector();
1030 std::move(after_data), std::move(description)));
1040 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 (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.
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