yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
pixel_editor_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <queue>
5
6#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
11
12namespace yaze {
13namespace editor {
14
16 // Canvas is initialized via member initializer list
17}
18
19void PixelEditorPanel::Draw(bool* p_open) {
20 // EditorPanel interface - delegate to existing Update() logic
21 // Top toolbar
23 ImGui::SameLine();
25
26 ImGui::Separator();
27
28 // Main content area with canvas and side panels
29 ImGui::BeginChild("##PixelEditorContent", ImVec2(0, -24), false);
30
31 // Color picker on the left
32 ImGui::BeginChild("##ColorPickerSide", ImVec2(120, 0), true);
34 ImGui::Separator();
36 ImGui::EndChild();
37
38 ImGui::SameLine();
39
40 // Main canvas
41 ImGui::BeginChild("##CanvasArea", ImVec2(0, 0), true,
42 ImGuiWindowFlags_HorizontalScrollbar);
43 DrawCanvas();
44 ImGui::EndChild();
45
46 ImGui::EndChild();
47
48 // Status bar
50}
51
53 // Top toolbar
55 ImGui::SameLine();
57
58 ImGui::Separator();
59
60 // Main content area with canvas and side panels
61 ImGui::BeginChild("##PixelEditorContent", ImVec2(0, -24), false);
62
63 // Color picker on the left
64 ImGui::BeginChild("##ColorPickerSide", ImVec2(200, 0), true);
66 ImGui::Separator();
68 ImGui::EndChild();
69
70 ImGui::SameLine();
71
72 // Main canvas
73 ImGui::BeginChild("##CanvasArea", ImVec2(0, 0), true,
74 ImGuiWindowFlags_HorizontalScrollbar);
75 DrawCanvas();
76 ImGui::EndChild();
77
78 ImGui::EndChild();
79
80 // Status bar
82
83 return absl::OkStatus();
84}
85
87 // Tool selection buttons
88 auto tool_button = [this](PixelTool tool, const char* icon,
89 const char* tooltip) {
90 bool is_selected = state_->current_tool == tool;
91 if (is_selected) {
92 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
93 }
94 if (ImGui::Button(icon)) {
95 state_->SetTool(tool);
96 }
97 if (is_selected) {
98 ImGui::PopStyleColor();
99 }
100 if (ImGui::IsItemHovered()) {
101 ImGui::SetTooltip("%s", tooltip);
102 }
103 ImGui::SameLine();
104 };
105
106 tool_button(PixelTool::kSelect, ICON_MD_SELECT_ALL, "Select (V)");
107 tool_button(PixelTool::kPencil, ICON_MD_DRAW, "Pencil (B)");
108 tool_button(PixelTool::kBrush, ICON_MD_BRUSH, "Brush (B)");
109 tool_button(PixelTool::kEraser, ICON_MD_AUTO_FIX_HIGH, "Eraser (E)");
110 tool_button(PixelTool::kFill, ICON_MD_FORMAT_COLOR_FILL, "Fill (G)");
111 tool_button(PixelTool::kLine, ICON_MD_HORIZONTAL_RULE, "Line");
112 tool_button(PixelTool::kRectangle, ICON_MD_CROP_SQUARE, "Rectangle");
113 tool_button(PixelTool::kEyedropper, ICON_MD_COLORIZE, "Eyedropper (I)");
114
115 ImGui::SameLine();
116 ImGui::Text("|");
117 ImGui::SameLine();
118
119 // Brush size for pencil/brush/eraser
123 ImGui::SetNextItemWidth(80);
124 int brush = state_->brush_size;
125 if (ImGui::SliderInt("##BrushSize", &brush, 1, 8, "%d px")) {
126 state_->brush_size = static_cast<uint8_t>(brush);
127 }
128 HOVER_HINT("Brush size");
129 ImGui::SameLine();
130 }
131
132 // Undo/Redo buttons
133 ImGui::Text("|");
134 ImGui::SameLine();
135
136 ImGui::BeginDisabled(!state_->CanUndo());
137 if (ImGui::Button(ICON_MD_UNDO)) {
138 PixelEditorSnapshot snapshot;
139 if (state_->PopUndoState(snapshot)) {
140 // Apply undo state
141 auto& sheet =
143 sheet.set_data(snapshot.pixel_data);
145 }
146 }
147 ImGui::EndDisabled();
148 HOVER_HINT("Undo (Ctrl+Z)");
149
150 ImGui::SameLine();
151
152 ImGui::BeginDisabled(!state_->CanRedo());
153 if (ImGui::Button(ICON_MD_REDO)) {
154 PixelEditorSnapshot snapshot;
155 if (state_->PopRedoState(snapshot)) {
156 // Apply redo state
157 auto& sheet =
159 sheet.set_data(snapshot.pixel_data);
161 }
162 }
163 ImGui::EndDisabled();
164 HOVER_HINT("Redo (Ctrl+Y)");
165}
166
168 // Zoom controls
169 if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
170 state_->ZoomOut();
171 }
172 HOVER_HINT("Zoom out (-)");
173 ImGui::SameLine();
174
175 ImGui::SetNextItemWidth(100);
176 float zoom = state_->zoom_level;
177 if (ImGui::SliderFloat("##Zoom", &zoom, 1.0f, 16.0f, "%.0fx")) {
178 state_->SetZoom(zoom);
179 }
180 ImGui::SameLine();
181
182 if (ImGui::Button(ICON_MD_ZOOM_IN)) {
183 state_->ZoomIn();
184 }
185 HOVER_HINT("Zoom in (+)");
186
187 ImGui::SameLine();
188 ImGui::Text("|");
189 ImGui::SameLine();
190
191 // View overlay toggles
192 ImGui::Checkbox(ICON_MD_GRID_ON, &state_->show_grid);
193 HOVER_HINT("Toggle grid (Ctrl+G)");
194 ImGui::SameLine();
195
196 ImGui::Checkbox(ICON_MD_ADD, &state_->show_cursor_crosshair);
197 HOVER_HINT("Toggle cursor crosshair");
198 ImGui::SameLine();
199
200 ImGui::Checkbox(ICON_MD_BRUSH, &state_->show_brush_preview);
201 HOVER_HINT("Toggle brush preview");
202 ImGui::SameLine();
203
205 HOVER_HINT("Toggle transparency grid");
206}
207
209 if (state_->open_sheets.empty()) {
210 ImGui::TextDisabled("No sheet selected. Select a sheet from the browser.");
211 return;
212 }
213
214 // Tab bar for open sheets
215 if (ImGui::BeginTabBar("##SheetTabs",
216 ImGuiTabBarFlags_AutoSelectNewTabs |
217 ImGuiTabBarFlags_Reorderable |
218 ImGuiTabBarFlags_TabListPopupButton)) {
219 std::vector<uint16_t> sheets_to_close;
220
221 for (uint16_t sheet_id : state_->open_sheets) {
222 bool open = true;
223 std::string tab_label = absl::StrFormat("%02X", sheet_id);
224 if (state_->modified_sheets.count(sheet_id) > 0) {
225 tab_label += "*";
226 }
227
228 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
229 state_->current_sheet_id = sheet_id;
230
231 // Get the current sheet bitmap
232 auto& sheet =
234
235 if (!sheet.is_active()) {
236 ImGui::TextDisabled("Sheet %02X is not active", sheet_id);
237 ImGui::EndTabItem();
238 continue;
239 }
240
241 // Calculate canvas size based on zoom
242 float canvas_width = sheet.width() * state_->zoom_level;
243 float canvas_height = sheet.height() * state_->zoom_level;
244
245 // Draw canvas background
246 canvas_.DrawBackground(ImVec2(canvas_width, canvas_height));
247
248 // Draw transparency checkerboard background if enabled
250 DrawTransparencyGrid(canvas_width, canvas_height);
251 }
252
253 // Draw the sheet texture
254 if (sheet.texture()) {
255 canvas_.draw_list()->AddImage(
256 (ImTextureID)(intptr_t)sheet.texture(),
258 ImVec2(canvas_.zero_point().x + canvas_width,
259 canvas_.zero_point().y + canvas_height));
260 }
261
262 // Draw grid if enabled
263 if (state_->show_grid) {
265 }
266
267 // Draw selection rectangle if active
269 ImVec2 sel_min = PixelToScreen(state_->selection.x, state_->selection.y);
270 ImVec2 sel_max =
273 canvas_.draw_list()->AddRect(sel_min, sel_max,
274 IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
275
276 // Marching ants effect (simplified)
277 canvas_.draw_list()->AddRect(sel_min, sel_max,
278 IM_COL32(0, 0, 0, 128), 0.0f, 0, 1.0f);
279 }
280
281 // Draw tool preview (line/rectangle)
283 ImVec2 start = PixelToScreen(static_cast<int>(tool_start_pixel_.x),
284 static_cast<int>(tool_start_pixel_.y));
285 ImVec2 end = PixelToScreen(static_cast<int>(preview_end_.x),
286 static_cast<int>(preview_end_.y));
287
289 canvas_.draw_list()->AddLine(start, end, IM_COL32(255, 255, 0, 200),
290 2.0f);
292 canvas_.draw_list()->AddRect(start, end, IM_COL32(255, 255, 0, 200),
293 0.0f, 0, 2.0f);
294 }
295 }
296
297 // Draw cursor crosshair overlay if enabled and cursor in canvas
300 }
301
302 // Draw brush preview if using brush/eraser tool
307 }
308
310
311 // Handle mouse input
313
314 // Show pixel info tooltip if enabled
317 }
318
319 ImGui::EndTabItem();
320 }
321
322 if (!open) {
323 sheets_to_close.push_back(sheet_id);
324 }
325 }
326
327 // Close tabs that were requested
328 for (uint16_t sheet_id : sheets_to_close) {
329 state_->CloseSheet(sheet_id);
330 }
331
332 ImGui::EndTabBar();
333 }
334}
335
337 float canvas_height) {
338 const float cell_size = 8.0f; // Checkerboard cell size
339 const ImU32 color1 = IM_COL32(180, 180, 180, 255);
340 const ImU32 color2 = IM_COL32(220, 220, 220, 255);
341
342 ImVec2 origin = canvas_.zero_point();
343 int cols = static_cast<int>(canvas_width / cell_size) + 1;
344 int rows = static_cast<int>(canvas_height / cell_size) + 1;
345
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));
352 canvas_.draw_list()->AddRectFilled(p_min, p_max,
353 is_light ? color1 : color2);
354 }
355 }
356}
357
359 ImVec2 cursor_screen = PixelToScreen(cursor_x_, cursor_y_);
360 float pixel_size = state_->zoom_level;
361
362 // Vertical line through cursor pixel
363 ImVec2 v_start(cursor_screen.x + pixel_size / 2,
364 canvas_.zero_point().y);
365 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
367 canvas_.draw_list()->AddLine(v_start, v_end, IM_COL32(255, 100, 100, 100),
368 1.0f);
369
370 // Horizontal line through cursor pixel
371 ImVec2 h_start(canvas_.zero_point().x,
372 cursor_screen.y + pixel_size / 2);
373 ImVec2 h_end(canvas_.zero_point().x + canvas_.canvas_size().x,
374 cursor_screen.y + pixel_size / 2);
375 canvas_.draw_list()->AddLine(h_start, h_end, IM_COL32(255, 100, 100, 100),
376 1.0f);
377
378 // Highlight current pixel with a bright outline
379 ImVec2 pixel_min = cursor_screen;
380 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
381 canvas_.draw_list()->AddRect(pixel_min, pixel_max,
382 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
383}
384
386 ImVec2 cursor_screen = PixelToScreen(cursor_x_, cursor_y_);
387 float pixel_size = state_->zoom_level;
388 int brush = state_->brush_size;
389 int half = brush / 2;
390
391 // Draw preview of brush area
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);
396
397 // Fill with semi-transparent color preview
398 ImU32 preview_color = (state_->current_tool == PixelTool::kEraser)
399 ? IM_COL32(255, 0, 0, 50)
400 : IM_COL32(0, 255, 0, 50);
401 canvas_.draw_list()->AddRectFilled(brush_min, brush_max, preview_color);
402
403 // Outline
404 ImU32 outline_color = (state_->current_tool == PixelTool::kEraser)
405 ? IM_COL32(255, 100, 100, 200)
406 : IM_COL32(100, 255, 100, 200);
407 canvas_.draw_list()->AddRect(brush_min, brush_max, outline_color, 0.0f, 0,
408 1.0f);
409}
410
412 if (cursor_x_ < 0 || cursor_x_ >= sheet.width() || cursor_y_ < 0 ||
413 cursor_y_ >= sheet.height()) {
414 return;
415 }
416
417 uint8_t color_index = sheet.GetPixel(cursor_x_, cursor_y_);
418 auto palette = sheet.palette();
419
420 ImGui::BeginTooltip();
421 ImGui::Text("Pos: %d, %d", cursor_x_, cursor_y_);
422 ImGui::Text("Tile: %d, %d", cursor_x_ / 8, cursor_y_ / 8);
423 ImGui::Text("Index: %d", color_index);
424
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,
431 ImVec2(24, 24));
432 if (color_index == 0) {
433 ImGui::SameLine();
434 ImGui::TextDisabled("(Transparent)");
435 }
436 }
437 ImGui::EndTooltip();
438}
439
441 ImGui::Text("Colors");
442
443 if (state_->open_sheets.empty()) {
444 ImGui::TextDisabled("No sheet");
445 return;
446 }
447
448 auto& sheet =
450 auto palette = sheet.palette();
451
452 // Draw palette colors in 4x4 grid (16 colors)
453 for (int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
454 if (i > 0 && i % 4 == 0) {
455 // New row
456 } else if (i > 0) {
457 ImGui::SameLine();
458 }
459
460 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
461 palette[i].rgb().z / 255.0f, 1.0f);
462
463 bool is_selected = state_->current_color_index == i;
464 if (is_selected) {
465 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
466 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
467 }
468
469 std::string id = absl::StrFormat("##Color%d", i);
470 if (ImGui::ColorButton(id.c_str(), color,
471 ImGuiColorEditFlags_NoTooltip |
472 ImGuiColorEditFlags_NoBorder,
473 ImVec2(24, 24))) {
474 state_->current_color_index = static_cast<uint8_t>(i);
475 state_->current_color = color;
476 }
477
478 if (is_selected) {
479 ImGui::PopStyleColor();
480 ImGui::PopStyleVar();
481 }
482
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));
490 if (i == 0) {
491 ImGui::Text("(Transparent)");
492 }
493 ImGui::EndTooltip();
494 }
495 }
496
497 ImGui::Separator();
498
499 // Current color preview
500 ImGui::Text("Current:");
501 ImGui::ColorButton("##CurrentColor", state_->current_color,
502 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
503 ImGui::SameLine();
504 ImGui::Text("Index: %d", state_->current_color_index);
505}
506
508 ImGui::Text("Navigator");
509
510 if (state_->open_sheets.empty()) {
511 ImGui::TextDisabled("No sheet");
512 return;
513 }
514
515 auto& sheet =
517 if (!sheet.texture()) return;
518
519 // Draw mini version of the sheet
520 float mini_scale = 0.5f;
521 float mini_width = sheet.width() * mini_scale;
522 float mini_height = sheet.height() * mini_scale;
523
524 ImVec2 pos = ImGui::GetCursorScreenPos();
525
526 ImGui::GetWindowDrawList()->AddImage((ImTextureID)(intptr_t)sheet.texture(),
527 pos,
528 ImVec2(pos.x + mini_width, pos.y + mini_height));
529
530 // Draw viewport rectangle
531 // TODO: Calculate actual viewport bounds based on scroll position
532
533 ImGui::Dummy(ImVec2(mini_width, mini_height));
534}
535
537 ImGui::Separator();
538
539 // Tool name
540 ImGui::Text("%s", state_->GetToolName());
541 ImGui::SameLine();
542
543 // Cursor position
544 if (cursor_in_canvas_) {
545 ImGui::Text("Pos: %d, %d", cursor_x_, cursor_y_);
546 ImGui::SameLine();
547
548 // Tile coordinates
549 int tile_x = cursor_x_ / 8;
550 int tile_y = cursor_y_ / 8;
551 ImGui::Text("Tile: %d, %d", tile_x, tile_y);
552 ImGui::SameLine();
553 }
554
555 // Sheet info
556 ImGui::Text("Sheet: %02X", state_->current_sheet_id);
557 ImGui::SameLine();
558
559 // Modified indicator
561 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "(Modified)");
562 }
563
564 // Zoom level
565 ImGui::SameLine();
566 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
567 ImGui::Text("Zoom: %.0fx", state_->zoom_level);
568}
569
571 if (!ImGui::IsItemHovered()) {
572 cursor_in_canvas_ = false;
573 return;
574 }
575
576 cursor_in_canvas_ = true;
577 ImVec2 mouse_pos = ImGui::GetMousePos();
578 ImVec2 pixel_pos = ScreenToPixel(mouse_pos);
579
580 cursor_x_ = static_cast<int>(pixel_pos.x);
581 cursor_y_ = static_cast<int>(pixel_pos.y);
582
583 auto& sheet =
585
586 // Clamp to sheet bounds
587 cursor_x_ = std::clamp(cursor_x_, 0, sheet.width() - 1);
588 cursor_y_ = std::clamp(cursor_y_, 0, sheet.height() - 1);
589
590 // Mouse button handling
591 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
592 is_drawing_ = true;
593 tool_start_pixel_ = ImVec2(static_cast<float>(cursor_x_),
594 static_cast<float>(cursor_y_));
596
597 // Save undo state before starting to draw
599
600 // Handle tools that need start position
601 switch (state_->current_tool) {
604 break;
607 break;
610 break;
611 case PixelTool::kFill:
613 break;
616 break;
619 break;
620 case PixelTool::kLine:
622 show_tool_preview_ = true;
623 break;
624 default:
625 break;
626 }
627 }
628
629 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) && is_drawing_) {
630 preview_end_ = ImVec2(static_cast<float>(cursor_x_),
631 static_cast<float>(cursor_y_));
632
633 switch (state_->current_tool) {
636 break;
639 break;
642 break;
645 break;
646 default:
647 break;
648 }
649
650 last_mouse_pixel_ = ImVec2(static_cast<float>(cursor_x_),
651 static_cast<float>(cursor_y_));
652 }
653
654 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && is_drawing_) {
655 is_drawing_ = false;
656
657 switch (state_->current_tool) {
658 case PixelTool::kLine:
659 DrawLine(static_cast<int>(tool_start_pixel_.x),
660 static_cast<int>(tool_start_pixel_.y), cursor_x_, cursor_y_);
661 break;
663 DrawRectangle(static_cast<int>(tool_start_pixel_.x),
664 static_cast<int>(tool_start_pixel_.y), cursor_x_,
665 cursor_y_, false);
666 break;
668 EndSelection();
669 break;
670 default:
671 break;
672 }
673
674 show_tool_preview_ = false;
675 }
676}
677
679 auto& sheet =
681
682 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
683 sheet.WriteToPixel(x, y, state_->current_color_index);
686 }
687}
688
690 auto& sheet =
692 int size = state_->brush_size;
693 int half = size / 2;
694
695 for (int dy = -half; dy < size - half; dy++) {
696 for (int dx = -half; dx < size - half; dx++) {
697 int px = x + dx;
698 int py = y + dy;
699 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
700 sheet.WriteToPixel(px, py, state_->current_color_index);
701 }
702 }
703 }
704
707}
708
710 auto& sheet =
712 int size = state_->brush_size;
713 int half = size / 2;
714
715 for (int dy = -half; dy < size - half; dy++) {
716 for (int dx = -half; dx < size - half; dx++) {
717 int px = x + dx;
718 int py = y + dy;
719 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
720 sheet.WriteToPixel(px, py, 0); // Index 0 = transparent
721 }
722 }
723 }
724
727}
728
729void PixelEditorPanel::ApplyFill(int x, int y) {
730 auto& sheet =
732
733 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height()) return;
734
735 uint8_t target_color = sheet.GetPixel(x, y);
736 uint8_t fill_color = state_->current_color_index;
737
738 if (target_color == fill_color) return; // Nothing to fill
739
740 // BFS flood fill
741 std::queue<std::pair<int, int>> queue;
742 std::vector<bool> visited(sheet.width() * sheet.height(), false);
743
744 queue.push({x, y});
745 visited[y * sheet.width() + x] = true;
746
747 while (!queue.empty()) {
748 auto [cx, cy] = queue.front();
749 queue.pop();
750
751 sheet.WriteToPixel(cx, cy, fill_color);
752
753 // Check 4-connected neighbors
754 const int dx[] = {0, 0, -1, 1};
755 const int dy[] = {-1, 1, 0, 0};
756
757 for (int i = 0; i < 4; i++) {
758 int nx = cx + dx[i];
759 int ny = cy + dy[i];
760
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) {
764 visited[idx] = true;
765 queue.push({nx, ny});
766 }
767 }
768 }
769 }
770
773}
774
777
778 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
779 state_->current_color_index = sheet.GetPixel(x, y);
780
781 // Update current color display
782 auto palette = sheet.palette();
783 if (state_->current_color_index < palette.size()) {
784 auto& color = palette[state_->current_color_index];
786 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
787 color.rgb().z / 255.0f, 1.0f);
788 }
789 }
790}
791
792void PixelEditorPanel::DrawLine(int x1, int y1, int x2, int y2) {
793 auto& sheet =
795
796 // Bresenham's line algorithm
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;
801 int err = dx - dy;
802
803 while (true) {
804 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
805 sheet.WriteToPixel(x1, y1, state_->current_color_index);
806 }
807
808 if (x1 == x2 && y1 == y2) break;
809
810 int e2 = 2 * err;
811 if (e2 > -dy) {
812 err -= dy;
813 x1 += sx;
814 }
815 if (e2 < dx) {
816 err += dx;
817 y1 += sy;
818 }
819 }
820
823}
824
825void PixelEditorPanel::DrawRectangle(int x1, int y1, int x2, int y2,
826 bool filled) {
827 auto& sheet =
829
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);
834
835 if (filled) {
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()) {
839 sheet.WriteToPixel(x, y, state_->current_color_index);
840 }
841 }
842 }
843 } else {
844 // Top and bottom edges
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())
848 sheet.WriteToPixel(x, min_y, state_->current_color_index);
849 if (max_y >= 0 && max_y < sheet.height())
850 sheet.WriteToPixel(x, max_y, state_->current_color_index);
851 }
852 }
853 // Left and right edges
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())
857 sheet.WriteToPixel(min_x, y, state_->current_color_index);
858 if (max_x >= 0 && max_x < sheet.width())
859 sheet.WriteToPixel(max_x, y, state_->current_color_index);
860 }
861 }
862 }
863
866}
867
869 state_->selection.x = x;
870 state_->selection.y = y;
873 state_->selection.is_active = true;
874 state_->is_selecting = true;
875}
876
878 int start_x = static_cast<int>(tool_start_pixel_.x);
879 int start_y = static_cast<int>(tool_start_pixel_.y);
880
881 state_->selection.x = std::min(start_x, x);
882 state_->selection.y = std::min(start_y, y);
883 state_->selection.width = std::abs(x - start_x) + 1;
884 state_->selection.height = std::abs(y - start_y) + 1;
885}
886
888 state_->is_selecting = false;
889
890 // Copy pixel data for the selection
891 if (state_->selection.width > 0 && state_->selection.height > 0) {
895
896 for (int y = 0; y < state_->selection.height; y++) {
897 for (int x = 0; x < state_->selection.width; x++) {
898 int src_x = state_->selection.x + x;
899 int src_y = state_->selection.y + y;
900 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
901 src_y < sheet.height()) {
903 sheet.GetPixel(src_x, src_y);
904 }
905 }
906 }
907
908 state_->selection.palette = sheet.palette();
909 }
910}
911
913 // Selection data is already in state_->selection
914}
915
917 if (state_->selection.pixel_data.empty()) return;
918
919 auto& sheet =
921
923
924 for (int dy = 0; dy < state_->selection.height; dy++) {
925 for (int dx = 0; dx < state_->selection.width; dx++) {
926 int dest_x = x + dx;
927 int dest_y = y + dy;
928 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
929 dest_y < sheet.height()) {
930 uint8_t pixel =
932 sheet.WriteToPixel(dest_x, dest_y, pixel);
933 }
934 }
935 }
936
939}
940
942 if (state_->selection.pixel_data.empty()) return;
943
944 std::vector<uint8_t> flipped(state_->selection.pixel_data.size());
945 for (int y = 0; y < state_->selection.height; y++) {
946 for (int x = 0; x < state_->selection.width; x++) {
947 int src_idx = y * state_->selection.width + x;
948 int dst_idx = y * state_->selection.width + (state_->selection.width - 1 - x);
949 flipped[dst_idx] = state_->selection.pixel_data[src_idx];
950 }
951 }
952 state_->selection.pixel_data = std::move(flipped);
953}
954
956 if (state_->selection.pixel_data.empty()) return;
957
958 std::vector<uint8_t> flipped(state_->selection.pixel_data.size());
959 for (int y = 0; y < state_->selection.height; y++) {
960 for (int x = 0; x < state_->selection.width; x++) {
961 int src_idx = y * state_->selection.width + x;
962 int dst_idx =
963 (state_->selection.height - 1 - y) * state_->selection.width + x;
964 flipped[dst_idx] = state_->selection.pixel_data[src_idx];
965 }
966 }
967 state_->selection.pixel_data = std::move(flipped);
968}
969
973 sheet.palette());
974}
975
976ImVec2 PixelEditorPanel::ScreenToPixel(ImVec2 screen_pos) {
977 float px = (screen_pos.x - canvas_.zero_point().x) / state_->zoom_level;
978 float py = (screen_pos.y - canvas_.zero_point().y) / state_->zoom_level;
979 return ImVec2(px, py);
980}
981
983 return ImVec2(canvas_.zero_point().x + x * state_->zoom_level,
985}
986
987} // namespace editor
988} // namespace yaze
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.
bool PopUndoState(PixelEditorSnapshot &out)
Pop and return the last undo state.
void SetZoom(float zoom)
Set zoom level with clamping.
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.
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.
Definition arena.h:127
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:101
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Definition arena.cc:313
static Arena & Get()
Definition arena.cc:19
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
int height() const
Definition bitmap.h:374
int width() const
Definition bitmap.h:373
uint8_t GetPixel(int x, int y) const
Get the palette index at the given x,y coordinates.
Definition bitmap.h:274
auto draw_list() const
Definition canvas.h:442
auto canvas_size() const
Definition canvas.h:451
auto zero_point() const
Definition canvas.h:443
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:547
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1398
#define ICON_MD_COLORIZE
Definition icons.h:441
#define ICON_MD_TEXTURE
Definition icons.h:1965
#define ICON_MD_DRAW
Definition icons.h:625
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_CROP_SQUARE
Definition icons.h:500
#define ICON_MD_HORIZONTAL_RULE
Definition icons.h:960
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_UNDO
Definition icons.h:2039
#define HOVER_HINT(string)
Definition macro.h:24
PixelTool
Pixel editing tool types for the graphics editor.
Snapshot for undo/redo operations.
std::vector< uint8_t > pixel_data