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 = gfx::Arena::Get().mutable_gfx_sheets()->at(
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(), canvas_.zero_point(),
257 ImVec2(canvas_.zero_point().x + canvas_width,
258 canvas_.zero_point().y + canvas_height));
259 }
260
261 // Draw grid if enabled
262 if (state_->show_grid) {
264 }
265
266 // Draw selection rectangle if active
268 ImVec2 sel_min =
270 ImVec2 sel_max =
273 canvas_.draw_list()->AddRect(
274 sel_min, sel_max, 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, IM_COL32(0, 0, 0, 128),
278 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, canvas_.zero_point().y);
364 ImVec2 v_end(cursor_screen.x + pixel_size / 2,
366 canvas_.draw_list()->AddLine(v_start, v_end, IM_COL32(255, 100, 100, 100),
367 1.0f);
368
369 // Horizontal line through cursor pixel
370 ImVec2 h_start(canvas_.zero_point().x, cursor_screen.y + pixel_size / 2);
371 ImVec2 h_end(canvas_.zero_point().x + canvas_.canvas_size().x,
372 cursor_screen.y + pixel_size / 2);
373 canvas_.draw_list()->AddLine(h_start, h_end, IM_COL32(255, 100, 100, 100),
374 1.0f);
375
376 // Highlight current pixel with a bright outline
377 ImVec2 pixel_min = cursor_screen;
378 ImVec2 pixel_max(cursor_screen.x + pixel_size, cursor_screen.y + pixel_size);
379 canvas_.draw_list()->AddRect(pixel_min, pixel_max,
380 IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
381}
382
384 ImVec2 cursor_screen = PixelToScreen(cursor_x_, cursor_y_);
385 float pixel_size = state_->zoom_level;
386 int brush = state_->brush_size;
387 int half = brush / 2;
388
389 // Draw preview of brush area
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);
394
395 // Fill with semi-transparent color preview
396 ImU32 preview_color = (state_->current_tool == PixelTool::kEraser)
397 ? IM_COL32(255, 0, 0, 50)
398 : IM_COL32(0, 255, 0, 50);
399 canvas_.draw_list()->AddRectFilled(brush_min, brush_max, preview_color);
400
401 // Outline
402 ImU32 outline_color = (state_->current_tool == PixelTool::kEraser)
403 ? IM_COL32(255, 100, 100, 200)
404 : IM_COL32(100, 255, 100, 200);
405 canvas_.draw_list()->AddRect(brush_min, brush_max, outline_color, 0.0f, 0,
406 1.0f);
407}
408
410 if (cursor_x_ < 0 || cursor_x_ >= sheet.width() || cursor_y_ < 0 ||
411 cursor_y_ >= sheet.height()) {
412 return;
413 }
414
415 uint8_t color_index = sheet.GetPixel(cursor_x_, cursor_y_);
416 auto palette = sheet.palette();
417
418 ImGui::BeginTooltip();
419 ImGui::Text("Pos: %d, %d", cursor_x_, cursor_y_);
420 ImGui::Text("Tile: %d, %d", cursor_x_ / 8, cursor_y_ / 8);
421 ImGui::Text("Index: %d", color_index);
422
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,
429 ImVec2(24, 24));
430 if (color_index == 0) {
431 ImGui::SameLine();
432 ImGui::TextDisabled("(Transparent)");
433 }
434 }
435 ImGui::EndTooltip();
436}
437
439 ImGui::Text("Colors");
440
441 if (state_->open_sheets.empty()) {
442 ImGui::TextDisabled("No sheet");
443 return;
444 }
445
446 auto& sheet =
448 auto palette = sheet.palette();
449
450 // Draw palette colors in 4x4 grid (16 colors)
451 for (int i = 0; i < static_cast<int>(palette.size()) && i < 16; i++) {
452 if (i > 0 && i % 4 == 0) {
453 // New row
454 } else if (i > 0) {
455 ImGui::SameLine();
456 }
457
458 ImVec4 color(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
459 palette[i].rgb().z / 255.0f, 1.0f);
460
461 bool is_selected = state_->current_color_index == i;
462 if (is_selected) {
463 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
464 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
465 }
466
467 std::string id = absl::StrFormat("##Color%d", i);
468 if (ImGui::ColorButton(
469 id.c_str(), color,
470 ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoBorder,
471 ImVec2(24, 24))) {
472 state_->current_color_index = static_cast<uint8_t>(i);
473 state_->current_color = color;
474 }
475
476 if (is_selected) {
477 ImGui::PopStyleColor();
478 ImGui::PopStyleVar();
479 }
480
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));
488 if (i == 0) {
489 ImGui::Text("(Transparent)");
490 }
491 ImGui::EndTooltip();
492 }
493 }
494
495 ImGui::Separator();
496
497 // Current color preview
498 ImGui::Text("Current:");
499 ImGui::ColorButton("##CurrentColor", state_->current_color,
500 ImGuiColorEditFlags_NoTooltip, ImVec2(40, 40));
501 ImGui::SameLine();
502 ImGui::Text("Index: %d", state_->current_color_index);
503}
504
506 ImGui::Text("Navigator");
507
508 if (state_->open_sheets.empty()) {
509 ImGui::TextDisabled("No sheet");
510 return;
511 }
512
513 auto& sheet =
515 if (!sheet.texture())
516 return;
517
518 // Draw mini version of the sheet
519 float mini_scale = 0.5f;
520 float mini_width = sheet.width() * mini_scale;
521 float mini_height = sheet.height() * mini_scale;
522
523 ImVec2 pos = ImGui::GetCursorScreenPos();
524
525 ImGui::GetWindowDrawList()->AddImage(
526 (ImTextureID)(intptr_t)sheet.texture(), pos,
527 ImVec2(pos.x + mini_width, pos.y + mini_height));
528
529 // Draw viewport rectangle
530 // TODO: Calculate actual viewport bounds based on scroll position
531
532 ImGui::Dummy(ImVec2(mini_width, mini_height));
533}
534
536 ImGui::Separator();
537
538 // Tool name
539 ImGui::Text("%s", state_->GetToolName());
540 ImGui::SameLine();
541
542 // Cursor position
543 if (cursor_in_canvas_) {
544 ImGui::Text("Pos: %d, %d", cursor_x_, cursor_y_);
545 ImGui::SameLine();
546
547 // Tile coordinates
548 int tile_x = cursor_x_ / 8;
549 int tile_y = cursor_y_ / 8;
550 ImGui::Text("Tile: %d, %d", tile_x, tile_y);
551 ImGui::SameLine();
552 }
553
554 // Sheet info
555 ImGui::Text("Sheet: %02X", state_->current_sheet_id);
556 ImGui::SameLine();
557
558 // Modified indicator
560 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "(Modified)");
561 }
562
563 // Zoom level
564 ImGui::SameLine();
565 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80);
566 ImGui::Text("Zoom: %.0fx", state_->zoom_level);
567}
568
570 if (!ImGui::IsItemHovered()) {
571 cursor_in_canvas_ = false;
572 return;
573 }
574
575 cursor_in_canvas_ = true;
576 ImVec2 mouse_pos = ImGui::GetMousePos();
577 ImVec2 pixel_pos = ScreenToPixel(mouse_pos);
578
579 cursor_x_ = static_cast<int>(pixel_pos.x);
580 cursor_y_ = static_cast<int>(pixel_pos.y);
581
582 auto& sheet =
584
585 // Clamp to sheet bounds
586 cursor_x_ = std::clamp(cursor_x_, 0, sheet.width() - 1);
587 cursor_y_ = std::clamp(cursor_y_, 0, sheet.height() - 1);
588
589 // Mouse button handling
590 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
591 is_drawing_ = true;
593 ImVec2(static_cast<float>(cursor_x_), static_cast<float>(cursor_y_));
595
596 // Save undo state before starting to draw
598
599 // Handle tools that need start position
600 switch (state_->current_tool) {
603 break;
606 break;
609 break;
610 case PixelTool::kFill:
612 break;
615 break;
618 break;
619 case PixelTool::kLine:
621 show_tool_preview_ = true;
622 break;
623 default:
624 break;
625 }
626 }
627
628 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left) && is_drawing_) {
630 ImVec2(static_cast<float>(cursor_x_), static_cast<float>(cursor_y_));
631
632 switch (state_->current_tool) {
635 break;
638 break;
641 break;
644 break;
645 default:
646 break;
647 }
648
650 ImVec2(static_cast<float>(cursor_x_), static_cast<float>(cursor_y_));
651 }
652
653 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && is_drawing_) {
654 is_drawing_ = false;
655
656 switch (state_->current_tool) {
657 case PixelTool::kLine:
658 DrawLine(static_cast<int>(tool_start_pixel_.x),
659 static_cast<int>(tool_start_pixel_.y), cursor_x_, cursor_y_);
660 break;
662 DrawRectangle(static_cast<int>(tool_start_pixel_.x),
663 static_cast<int>(tool_start_pixel_.y), cursor_x_,
664 cursor_y_, false);
665 break;
667 EndSelection();
668 break;
669 default:
670 break;
671 }
672
673 show_tool_preview_ = false;
674 }
675}
676
678 auto& sheet =
680
681 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
682 sheet.WriteToPixel(x, y, state_->current_color_index);
685 }
686}
687
689 auto& sheet =
691 int size = state_->brush_size;
692 int half = size / 2;
693
694 for (int dy = -half; dy < size - half; dy++) {
695 for (int dx = -half; dx < size - half; dx++) {
696 int px = x + dx;
697 int py = y + dy;
698 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
699 sheet.WriteToPixel(px, py, state_->current_color_index);
700 }
701 }
702 }
703
706}
707
709 auto& sheet =
711 int size = state_->brush_size;
712 int half = size / 2;
713
714 for (int dy = -half; dy < size - half; dy++) {
715 for (int dx = -half; dx < size - half; dx++) {
716 int px = x + dx;
717 int py = y + dy;
718 if (px >= 0 && px < sheet.width() && py >= 0 && py < sheet.height()) {
719 sheet.WriteToPixel(px, py, 0); // Index 0 = transparent
720 }
721 }
722 }
723
726}
727
728void PixelEditorPanel::ApplyFill(int x, int y) {
729 auto& sheet =
731
732 if (x < 0 || x >= sheet.width() || y < 0 || y >= sheet.height())
733 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)
739 return; // Nothing to fill
740
741 // BFS flood fill
742 std::queue<std::pair<int, int>> queue;
743 std::vector<bool> visited(sheet.width() * sheet.height(), false);
744
745 queue.push({x, y});
746 visited[y * sheet.width() + x] = true;
747
748 while (!queue.empty()) {
749 auto [cx, cy] = queue.front();
750 queue.pop();
751
752 sheet.WriteToPixel(cx, cy, fill_color);
753
754 // Check 4-connected neighbors
755 const int dx[] = {0, 0, -1, 1};
756 const int dy[] = {-1, 1, 0, 0};
757
758 for (int i = 0; i < 4; i++) {
759 int nx = cx + dx[i];
760 int ny = cy + dy[i];
761
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) {
765 visited[idx] = true;
766 queue.push({nx, ny});
767 }
768 }
769 }
770 }
771
774}
775
778
779 if (x >= 0 && x < sheet.width() && y >= 0 && y < sheet.height()) {
780 state_->current_color_index = sheet.GetPixel(x, y);
781
782 // Update current color display
783 auto palette = sheet.palette();
784 if (state_->current_color_index < palette.size()) {
785 auto& color = palette[state_->current_color_index];
787 ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
788 color.rgb().z / 255.0f, 1.0f);
789 }
790 }
791}
792
793void PixelEditorPanel::DrawLine(int x1, int y1, int x2, int y2) {
794 auto& sheet =
796
797 // Bresenham's line algorithm
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;
802 int err = dx - dy;
803
804 while (true) {
805 if (x1 >= 0 && x1 < sheet.width() && y1 >= 0 && y1 < sheet.height()) {
806 sheet.WriteToPixel(x1, y1, state_->current_color_index);
807 }
808
809 if (x1 == x2 && y1 == y2)
810 break;
811
812 int e2 = 2 * err;
813 if (e2 > -dy) {
814 err -= dy;
815 x1 += sx;
816 }
817 if (e2 < dx) {
818 err += dx;
819 y1 += sy;
820 }
821 }
822
825}
826
827void PixelEditorPanel::DrawRectangle(int x1, int y1, int x2, int y2,
828 bool filled) {
829 auto& sheet =
831
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);
836
837 if (filled) {
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()) {
841 sheet.WriteToPixel(x, y, state_->current_color_index);
842 }
843 }
844 }
845 } else {
846 // Top and bottom edges
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())
850 sheet.WriteToPixel(x, min_y, state_->current_color_index);
851 if (max_y >= 0 && max_y < sheet.height())
852 sheet.WriteToPixel(x, max_y, state_->current_color_index);
853 }
854 }
855 // Left and right edges
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())
859 sheet.WriteToPixel(min_x, y, state_->current_color_index);
860 if (max_x >= 0 && max_x < sheet.width())
861 sheet.WriteToPixel(max_x, y, state_->current_color_index);
862 }
863 }
864 }
865
868}
869
871 state_->selection.x = x;
872 state_->selection.y = y;
875 state_->selection.is_active = true;
876 state_->is_selecting = true;
877}
878
880 int start_x = static_cast<int>(tool_start_pixel_.x);
881 int start_y = static_cast<int>(tool_start_pixel_.y);
882
883 state_->selection.x = std::min(start_x, x);
884 state_->selection.y = std::min(start_y, y);
885 state_->selection.width = std::abs(x - start_x) + 1;
886 state_->selection.height = std::abs(y - start_y) + 1;
887}
888
890 state_->is_selecting = false;
891
892 // Copy pixel data for the selection
893 if (state_->selection.width > 0 && state_->selection.height > 0) {
897
898 for (int y = 0; y < state_->selection.height; y++) {
899 for (int x = 0; x < state_->selection.width; x++) {
900 int src_x = state_->selection.x + x;
901 int src_y = state_->selection.y + y;
902 if (src_x >= 0 && src_x < sheet.width() && src_y >= 0 &&
903 src_y < sheet.height()) {
905 sheet.GetPixel(src_x, src_y);
906 }
907 }
908 }
909
910 state_->selection.palette = sheet.palette();
911 }
912}
913
915 // Selection data is already in state_->selection
916}
917
919 if (state_->selection.pixel_data.empty())
920 return;
921
922 auto& sheet =
924
926
927 for (int dy = 0; dy < state_->selection.height; dy++) {
928 for (int dx = 0; dx < state_->selection.width; dx++) {
929 int dest_x = x + dx;
930 int dest_y = y + dy;
931 if (dest_x >= 0 && dest_x < sheet.width() && dest_y >= 0 &&
932 dest_y < sheet.height()) {
933 uint8_t pixel =
935 sheet.WriteToPixel(dest_x, dest_y, pixel);
936 }
937 }
938 }
939
942}
943
945 if (state_->selection.pixel_data.empty())
946 return;
947
948 std::vector<uint8_t> flipped(state_->selection.pixel_data.size());
949 for (int y = 0; y < state_->selection.height; y++) {
950 for (int x = 0; x < state_->selection.width; x++) {
951 int src_idx = y * state_->selection.width + x;
952 int dst_idx =
953 y * state_->selection.width + (state_->selection.width - 1 - x);
954 flipped[dst_idx] = state_->selection.pixel_data[src_idx];
955 }
956 }
957 state_->selection.pixel_data = std::move(flipped);
958}
959
961 if (state_->selection.pixel_data.empty())
962 return;
963
964 std::vector<uint8_t> flipped(state_->selection.pixel_data.size());
965 for (int y = 0; y < state_->selection.height; y++) {
966 for (int x = 0; x < state_->selection.width; x++) {
967 int src_idx = y * state_->selection.width + x;
968 int dst_idx =
969 (state_->selection.height - 1 - y) * state_->selection.width + x;
970 flipped[dst_idx] = state_->selection.pixel_data[src_idx];
971 }
972 }
973 state_->selection.pixel_data = std::move(flipped);
974}
975
979 sheet.palette());
980}
981
982ImVec2 PixelEditorPanel::ScreenToPixel(ImVec2 screen_pos) {
983 float px = (screen_pos.x - canvas_.zero_point().x) / state_->zoom_level;
984 float py = (screen_pos.y - canvas_.zero_point().y) / state_->zoom_level;
985 return ImVec2(px, py);
986}
987
989 return ImVec2(canvas_.zero_point().x + x * state_->zoom_level,
991}
992
993} // namespace editor
994} // 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:128
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:102
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Definition arena.cc:323
static Arena & Get()
Definition arena.cc:20
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