9#include "absl/strings/str_format.h"
19#include "imgui/imgui.h"
41 constexpr float kColorPickerWidth = 200.0f;
42 constexpr float kStatusBarHeight = 24.0f;
45 ImGui::BeginChild(
"##PixelEditorContent", ImVec2(0, -kStatusBarHeight),
49 ImGui::BeginChild(
"##ColorPickerSide", ImVec2(kColorPickerWidth, 0),
true);
58 ImGui::BeginChild(
"##CanvasArea", ImVec2(0, 0),
true,
59 ImGuiWindowFlags_HorizontalScrollbar);
68 return absl::OkStatus();
73 auto tool_button = [
this](
PixelTool tool,
const char* icon,
74 const char* tooltip) {
76 std::optional<gui::StyleColorGuard> sel_guard;
80 if (ImGui::Button(icon)) {
84 if (ImGui::IsItemHovered()) {
85 ImGui::SetTooltip(
"%s", tooltip);
107 ImGui::SetNextItemWidth(80);
109 if (ImGui::SliderInt(
"##BrushSize", &brush, 1, 8,
"%d px")) {
124 ImGui::EndDisabled();
132 ImGui::EndDisabled();
142 ImGui::SetNextItemWidth(100);
144 if (ImGui::SliderFloat(
"##Zoom", &zoom, 1.0f, 16.0f,
"%.0fx")) {
178 opts.
title =
"Select a sheet";
179 opts.
detail =
"Open a graphics sheet from the browser to edit pixels here.";
187 ImGuiTabBarFlags_AutoSelectNewTabs |
188 ImGuiTabBarFlags_Reorderable |
189 ImGuiTabBarFlags_TabListPopupButton)) {
190 std::vector<uint16_t> sheets_to_close;
194 std::string tab_label = absl::StrFormat(
"%02X", sheet_id);
199 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
206 if (!sheet.is_active()) {
207 ImGui::TextDisabled(tr(
"Sheet %02X is not active"), sheet_id);
225 if (sheet.texture()) {
248 sel_min, sel_max, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
297 sheets_to_close.push_back(sheet_id);
302 for (uint16_t sheet_id : sheets_to_close) {
311 float canvas_height) {
312 const float cell_size = 8.0f;
313 const ImU32 color1 = IM_COL32(180, 180, 180, 255);
314 const ImU32 color2 = IM_COL32(220, 220, 220, 255);
317 int cols =
static_cast<int>(canvas_width / cell_size) + 1;
318 int rows =
static_cast<int>(canvas_height / cell_size) + 1;
320 for (
int row = 0; row < rows; row++) {
321 for (
int col = 0; col < cols; col++) {
322 bool is_light = (row + col) % 2 == 0;
323 ImVec2 p_min(origin.x + col * cell_size, origin.y + row * cell_size);
324 ImVec2 p_max(std::min(p_min.x + cell_size, origin.x + canvas_width),
325 std::min(p_min.y + cell_size, origin.y + canvas_height));
327 is_light ? color1 : color2);
338 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
346 cursor_screen.y + pixel_size / 2);
351 ImVec2 pixel_min = cursor_screen;
352 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
354 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
361 int half = brush / 2;
364 ImVec2 brush_min(cursor_screen.x - half * pixel_size,
365 cursor_screen.y - half * pixel_size);
366 ImVec2 brush_max(cursor_screen.x + (brush - half) * pixel_size,
367 cursor_screen.y + (brush - half) * pixel_size);
371 ? IM_COL32(255, 0, 0, 50)
372 : IM_COL32(0, 255, 0, 50);
377 ? IM_COL32(255, 100, 100, 200)
378 : IM_COL32(100, 255, 100, 200);
384 if (cursor_x_ < 0 || cursor_x_ >= sheet.
width() ||
cursor_y_ < 0 ||
390 auto palette = sheet.
palette();
392 ImGui::BeginTooltip();
395 ImGui::Text(tr(
"Index: %d"), color_index);
397 if (color_index < palette.size()) {
398 ImGui::Text(tr(
"SNES: $%04X"), palette[color_index].snes());
399 ImVec4 color(palette[color_index].rgb().x / 255.0f,
400 palette[color_index].rgb().y / 255.0f,
401 palette[color_index].rgb().z / 255.0f, 1.0f);
402 ImGui::ColorButton(
"##ColorPreview", color, ImGuiColorEditFlags_NoTooltip,
404 if (color_index == 0) {
406 ImGui::TextDisabled(tr(
"(Transparent)"));
420 const double now = ImGui::GetTime();
427 const int tiles_per_row = sheet.
width() / 8;
428 if (tiles_per_row <= 0) {
432 const int tile_x =
static_cast<int>(tile_index % tiles_per_row);
433 const int tile_y =
static_cast<int>(tile_index / tiles_per_row);
436 0.5f + 0.5f * std::sin(
static_cast<float>(elapsed) * 6.0f);
437 const float alpha = 0.25f + (pulse * 0.35f);
444 const ImU32 fill_color = ImGui::GetColorU32(sel);
446 sel_outline.w = 0.9f;
447 const ImU32 outline_color = ImGui::GetColorU32(sel_outline);
451 if (ImGui::IsMouseHoveringRect(min, max)) {
452 ImGui::BeginTooltip();
453 ImGui::Text(tr(
"Focus tile: %d (sheet %02X)"), tile_index,
463 ImGui::Text(tr(
"Colors"));
466 ImGui::TextDisabled(tr(
"No sheet"));
472 auto palette = sheet.palette();
475 for (
int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
476 if (i > 0 && i % 4 == 0) {
482 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
483 palette[i].rgb().z / 255.0f, 1.0f);
486 std::optional<gui::StyleVarGuard> border_var;
487 std::optional<gui::StyleColorGuard> border_color;
489 border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
493 std::string
id = absl::StrFormat(
"##Color%d", i);
494 if (ImGui::ColorButton(
496 ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoBorder,
503 border_color.reset();
507 if (ImGui::IsItemHovered()) {
508 ImGui::BeginTooltip();
509 ImGui::Text(tr(
"Index: %d"), i);
510 ImGui::Text(tr(
"SNES: $%04X"), palette[i].snes());
511 ImGui::Text(tr(
"RGB: %d, %d, %d"),
static_cast<int>(palette[i].rgb().x),
512 static_cast<int>(palette[i].rgb().y),
513 static_cast<int>(palette[i].rgb().z));
515 ImGui::Text(tr(
"(Transparent)"));
524 ImGui::Text(tr(
"Current:"));
526 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
532 ImGui::Text(tr(
"Navigator"));
535 ImGui::TextDisabled(tr(
"No sheet"));
541 if (!sheet.texture())
545 float mini_scale = 0.5f;
546 float mini_width = sheet.width() * mini_scale;
547 float mini_height = sheet.height() * mini_scale;
549 ImVec2 pos = ImGui::GetCursorScreenPos();
551 ImGui::GetWindowDrawList()->AddImage(
552 (ImTextureID)(intptr_t)sheet.texture(), pos,
553 ImVec2(pos.x + mini_width, pos.y + mini_height));
558 ImGui::Dummy(ImVec2(mini_width, mini_height));
576 ImGui::Text(tr(
"Tile: %d, %d"), tile_x, tile_y);
598 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
603 if (!ImGui::IsItemHovered()) {
609 ImVec2 mouse_pos = ImGui::GetMousePos();
612 cursor_x_ =
static_cast<int>(pixel_pos.x);
613 cursor_y_ =
static_cast<int>(pixel_pos.y);
623 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
661 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
is_drawing_) {
686 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
is_drawing_) {
717 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
730 for (
int dy = -half; dy < size - half; dy++) {
731 for (
int dx = -half; dx < size - half; dx++) {
734 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
750 for (
int dy = -half; dy < size - half; dy++) {
751 for (
int dx = -half; dx < size - half; dx++) {
754 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
755 sheet.WriteToPixel(px, py, 0);
768 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
771 uint8_t target_color = sheet.GetPixel(x, y);
774 if (target_color == fill_color)
778 std::queue<std::pair<int, int>> queue;
779 std::vector<bool> visited(sheet.width() * sheet.height(),
false);
782 visited[y * sheet.width() + x] =
true;
784 while (!queue.empty()) {
785 auto [cx, cy] = queue.front();
788 sheet.WriteToPixel(cx, cy, fill_color);
791 const int dx[] = {0, 0, -1, 1};
792 const int dy[] = {-1, 1, 0, 0};
794 for (
int i = 0; i < 4; i++) {
798 if (nx >= 0 && nx < sheet.width() && ny >= 0 && ny < sheet.height()) {
799 int idx = ny * sheet.width() + nx;
800 if (!visited[idx] && sheet.GetPixel(nx, ny) == target_color) {
802 queue.push({nx, ny});
815 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
819 auto palette = sheet.palette();
823 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
824 color.rgb().z / 255.0f, 1.0f);
834 int dx = std::abs(x2 - x1);
835 int dy = std::abs(y2 - y1);
836 int sx = x1 < x2 ? 1 : -1;
837 int sy = y1 < y2 ? 1 : -1;
841 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
845 if (x1 == x2 && y1 == y2)
868 int min_x = std::min(x1, x2);
869 int max_x = std::max(x1, x2);
870 int min_y = std::min(y1, y2);
871 int max_y = std::max(y1, y2);
874 for (
int y = min_y; y <= max_y; y++) {
875 for (
int x = min_x; x <= max_x; x++) {
876 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
883 for (
int x = min_x; x <= max_x; x++) {
884 if (x >= 0 && x < sheet.width()) {
885 if (min_y >= 0 && min_y < sheet.height())
887 if (max_y >= 0 && max_y < sheet.height())
892 for (
int y = min_y; y <= max_y; y++) {
893 if (y >= 0 && y < sheet.height()) {
894 if (min_x >= 0 && min_x < sheet.width())
896 if (max_x >= 0 && max_x < sheet.width())
938 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
939 src_y < sheet.height()) {
941 sheet.GetPixel(src_x, src_y);
967 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
968 dest_y < sheet.height()) {
971 sheet.WriteToPixel(dest_x, dest_y, pixel);
1029 auto after_data = sheet.vector();
1037 std::move(after_data), std::move(description),
1038 [state =
state_](uint16_t sheet_id) {
1039 state->MarkSheetModified(sheet_id);
1050 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.
void ApplyFill(int x, int y)
Apply flood fill starting at position.
void DrawBrushPreview()
Draw brush size preview circle.
void DrawCursorCrosshair()
Draw crosshair at cursor position.
absl::Status Update()
Legacy Update method for backward compatibility.
void DrawCanvas()
Draw the main editing canvas.
void ApplyPencil(int x, int y)
Apply pencil tool at position.
std::vector< uint8_t > pending_undo_before_data_
void FinalizeUndoAction()
Finalize the current undo action by capturing the after-snapshot and pushing a GraphicsPixelEditActio...
void BeginSelection(int x, int y)
Start a new selection.
void HandleCanvasInput()
Handle canvas mouse input for current tool.
void DrawTransparencyGrid(float canvas_width, float canvas_height)
Draw checkerboard pattern for transparent pixels.
void Initialize()
Initialize the view.
GraphicsEditorState * state_
void ApplyEyedropper(int x, int y)
Apply eyedropper tool at position.
ImVec2 PixelToScreen(int x, int y)
Convert pixel coordinates to screen coordinates.
void DrawPixelInfoTooltip(const gfx::Bitmap &sheet)
Draw tooltip with pixel information.
void FlipSelectionHorizontal()
Flip selection horizontally.
ImVec2 ScreenToPixel(ImVec2 screen_pos)
Convert screen coordinates to pixel coordinates.
void DrawTileHighlight(const gfx::Bitmap &sheet)
Draw a transient highlight for a target tile.
void PasteSelection(int x, int y)
Paste clipboard at position.
void Draw(bool *p_open) override
Draw the pixel editor UI (WindowContent interface)
void DrawLine(int x1, int y1, int x2, int y2)
Draw line from start to end.
void CopySelection()
Copy selection to clipboard.
void UpdateSelection(int x, int y)
Update selection during drag.
void FlipSelectionVertical()
Flip selection vertically.
void DrawToolbar()
Draw the toolbar with tool selection.
void DrawViewControls()
Draw zoom and view controls.
void SaveUndoState()
Save current state for undo (captures before-snapshot)
void DrawRectangle(int x1, int y1, int x2, int y2, bool filled)
Draw rectangle from start to end.
uint16_t pending_undo_sheet_id_
void EndSelection()
Finalize the selection.
UndoManager * undo_manager_
void ApplyBrush(int x, int y)
Apply brush tool at position.
void DrawStatusBar()
Draw the status bar with cursor position.
void ApplyEraser(int x, int y)
Apply eraser tool at position.
void DrawColorPicker()
Draw the color palette picker.
void DrawMiniMap()
Draw the mini navigation map.
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()
bool DrawEmptyState(const EmptyStateOptions &options)
Draw a centered empty-state block.
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
Shared empty / disabled panel presentation.
bool compact
Tighter vertical rhythm for drawers / inspectors.