diff --git a/src/app/core/constants.h b/src/app/core/constants.h index 4c698c76..1ae9eab5 100644 --- a/src/app/core/constants.h +++ b/src/app/core/constants.h @@ -117,6 +117,8 @@ namespace yaze { namespace app { namespace core { +constexpr float kYazeVersion = 0.05; + // ============================================================================ // Window Variables // ============================================================================ diff --git a/src/app/editor/dungeon_editor.cc b/src/app/editor/dungeon_editor.cc index f52716d8..8a21d88c 100644 --- a/src/app/editor/dungeon_editor.cc +++ b/src/app/editor/dungeon_editor.cc @@ -7,6 +7,7 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/rom.h" +#include "app/zelda3/dungeon/object_names.h" #include "app/zelda3/dungeon/room_names.h" #include "zelda3/dungeon/room.h" @@ -32,6 +33,7 @@ absl::Status DungeonEditor::Update() { } DrawToolset(); + DrawObjectRenderer(); ImGui::Separator(); if (ImGui::BeginTable("#DungeonEditTable", 3, toolset_table_flags_, @@ -197,9 +199,8 @@ void DungeonEditor::DrawToolset() { ImGui::Button(ICON_MD_PEST_CONTROL_RODENT); ImGui::TableNextColumn(); - if (ImGui::Button("Load Dungeon Objects")) { - // object_renderer_.CreateVramFromRoomBlockset(); - object_renderer_.RenderObjectsAsBitmaps(); + if (ImGui::Button("Dungeon Object Renderer")) { + show_object_render_ = !show_object_render_; } ImGui::EndTable(); } @@ -229,6 +230,47 @@ void DungeonEditor::DrawTileSelector() { } } +void DungeonEditor::DrawObjectRenderer() { + if (show_object_render_) { + ImGui::Begin("Dungeon Object Renderer", &show_object_render_); + + // Create an ImGui table where the left side of the table is a matrix of + // buttons which represent each dungeon object. The right side of the table + // is a canvas which will display the selected dungeon object. The canvas + // will also have a tile selector and a grid overlay. + if (ImGui::BeginTable("DungeonObjectEditorTable", 2, + ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) { + ImGui::TableSetupColumn("Dungeon Objects", + ImGuiTableColumnFlags_WidthFixed, 150.0f); + ImGui::TableSetupColumn("Canvas"); + + ImGui::TableNextColumn(); + ImGui::BeginChild("DungeonObjectButtons", ImVec2(150.0f, 0), true); + + for (const auto each : zelda3::dungeon::Type1RoomObjectNames) { + ImGui::Button(each.data()); + if (ImGui::IsItemClicked()) { + } + } + + ImGui::EndChild(); + + // Right side of the table - Canvas + ImGui::TableNextColumn(); + ImGui::BeginChild("DungeonObjectCanvas", ImVec2(0, 0), true); + + // TODO: Insert code to display canvas, tile selector, and grid overlay + // here + + ImGui::EndChild(); + + ImGui::EndTable(); + } + + ImGui::End(); + } +} + } // namespace editor } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/editor/dungeon_editor.h b/src/app/editor/dungeon_editor.h index 1d0a63d1..c0413dce 100644 --- a/src/app/editor/dungeon_editor.h +++ b/src/app/editor/dungeon_editor.h @@ -34,8 +34,11 @@ class DungeonEditor : public Editor, void DrawRoomGraphics(); void DrawTileSelector(); + void DrawObjectRenderer(); + uint16_t current_room_id_ = 0; bool is_loaded_ = false; + bool show_object_render_ = false; gfx::Bitmap room_gfx_bmp_; @@ -46,9 +49,9 @@ class DungeonEditor : public Editor, gui::Canvas canvas_; gui::Canvas room_gfx_canvas_; - ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit | - ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Resizable; + ImGuiTableFlags toolset_table_flags_ = + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Reorderable | + ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable; }; } // namespace editor diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc index d38b1c58..01f24110 100644 --- a/src/app/editor/master_editor.cc +++ b/src/app/editor/master_editor.cc @@ -24,6 +24,7 @@ #include "app/gui/canvas.h" #include "app/gui/icons.h" #include "app/gui/input.h" +#include "app/gui/style.h" #include "app/gui/widgets.h" #include "app/rom.h" @@ -176,12 +177,45 @@ void MasterEditor::DrawInfoPopup() { } void MasterEditor::DrawYazeMenu() { + static bool show_display_settings = false; + static bool show_command_line_interface = false; + MENU_BAR() DrawFileMenu(); DrawEditMenu(); DrawViewMenu(); DrawHelpMenu(); + + ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetStyle().ItemSpacing.x - + ImGui::CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150); + // Modify the style of the button to have no background color + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + if (ImGui::Button(ICON_MD_DISPLAY_SETTINGS)) { + show_display_settings = !show_display_settings; + } + + if (ImGui::Button(ICON_MD_TERMINAL)) { + show_command_line_interface = !show_command_line_interface; + } + ImGui::PopStyleColor(); + + ImGui::Text(absl::StrCat("yaze v", core::kYazeVersion).c_str()); + END_MENU_BAR() + + if (show_display_settings) { + ImGui::Begin("Display Settings", &show_display_settings, + ImGuiWindowFlags_None); + gui::DrawDisplaySettings(); + ImGui::End(); + } + + if (show_command_line_interface) { + ImGui::Begin("Command Line Interface", &show_command_line_interface, + ImGuiWindowFlags_None); + ImGui::Text("Enter a command:"); + ImGui::End(); + } } void MasterEditor::DrawFileMenu() { @@ -266,7 +300,6 @@ void MasterEditor::DrawEditMenu() { void MasterEditor::DrawViewMenu() { static bool show_imgui_metrics = false; - static bool show_imgui_style_editor = false; static bool show_memory_editor = false; static bool show_asm_editor = false; static bool show_imgui_demo = false; @@ -303,12 +336,6 @@ void MasterEditor::DrawViewMenu() { ImGui::End(); } - if (show_imgui_style_editor) { - ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor); - ImGui::ShowStyleEditor(); - ImGui::End(); - } - if (show_memory_viewer) { ImGui::Begin("Memory Viewer (ImGui)", &show_memory_viewer); @@ -339,13 +366,7 @@ void MasterEditor::DrawViewMenu() { ImGui::MenuItem("Palette Editor", nullptr, &show_palette_editor); ImGui::MenuItem("Memory Viewer", nullptr, &show_memory_viewer); ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo); - ImGui::Separator(); - if (ImGui::BeginMenu("GUI Tools")) { - ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics); - ImGui::MenuItem("Style Editor (ImGui)", nullptr, - &show_imgui_style_editor); - ImGui::EndMenu(); - } + ImGui::MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); ImGui::EndMenu(); } } diff --git a/src/app/editor/overworld_editor.cc b/src/app/editor/overworld_editor.cc index ee23c733..a010efe2 100644 --- a/src/app/editor/overworld_editor.cc +++ b/src/app/editor/overworld_editor.cc @@ -55,7 +55,7 @@ absl::Status OverworldEditor::Update() { if (ImGui::BeginTable(kOWEditTable.data(), 2, kOWEditFlags, ImVec2(0, 0))) { TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x); - TableSetupColumn("Tile Selector"); + TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256); TableHeadersRow(); TableNextRow(); TableNextColumn(); diff --git a/src/app/editor/overworld_editor.h b/src/app/editor/overworld_editor.h index 0b575be8..ba63a41d 100644 --- a/src/app/editor/overworld_editor.h +++ b/src/app/editor/overworld_editor.h @@ -48,7 +48,7 @@ static constexpr absl::string_view kOverworldSettingsColumnNames[] = { constexpr ImGuiTableFlags kOWMapFlags = ImGuiTableFlags_Borders; constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit; constexpr ImGuiTableFlags kOWEditFlags = ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Resizable | + ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_SizingStretchSame; constexpr absl::string_view kWorldList = @@ -184,10 +184,6 @@ class OverworldEditor : public Editor, gfx::BitmapTable graphics_bin_; gfx::BitmapTable current_graphics_set_; gfx::BitmapTable sprite_previews_; - - ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Resizable | - ImGuiTableFlags_SizingStretchSame; }; } // namespace editor } // namespace app diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 2b4f96e9..7c6de4b6 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -1,12 +1,14 @@ #include "app/emu/emulator.h" #include +#include #include #include #include "app/core/constants.h" #include "app/emu/snes.h" +#include "app/gui/icons.h" #include "app/rom.h" namespace yaze { @@ -20,6 +22,65 @@ bool ShouldDisplay(const InstructionEntry& entry, const char* filter, // filter and showAll flag return true; } + +void DrawMemoryWindow(Memory* memory) { + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + static ImGuiTableFlags flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | + ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; + if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f); + ImGui::BeginTable("table1", 4, flags, outer_size)) { + // Table headers + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Memory Area"); + ImGui::TableNextColumn(); + ImGui::Text("Start Address"); + ImGui::TableNextColumn(); + ImGui::Text("Size"); + ImGui::TableNextColumn(); + ImGui::Text("Mapping"); + + // Retrieve memory information from MemoryImpl + MemoryImpl* memoryImpl = dynamic_cast(memory); + if (memoryImpl) { + // Display memory areas + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ROM"); + ImGui::TableNextColumn(); + ImGui::Text("0x000000"); + ImGui::TableNextColumn(); + ImGui::Text("%d MB", memoryImpl->rom_.size()); + ImGui::TableNextColumn(); + ImGui::Text("LoROM"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("RAM"); + ImGui::TableNextColumn(); + ImGui::Text("0x7E0000"); + ImGui::TableNextColumn(); + ImGui::Text("%d KB", memoryImpl->ram_.size()); + ImGui::TableNextColumn(); + ImGui::Text("LoROM"); + } + + ImGui::EndTable(); + + if (ImGui::Button("Open Memory Viewer", ImVec2(200, 50))) { + ImGui::OpenPopup("Memory Viewer"); + } + + if (ImGui::BeginPopupModal("Memory Viewer", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + static MemoryEditor mem_edit; + mem_edit.DrawContents((void*)memoryImpl->data(), memoryImpl->size()); + ImGui::EndPopup(); + } + } +} } // namespace using ImGui::NextColumn; @@ -30,7 +91,12 @@ using ImGui::Text; void Emulator::Run() { if (!snes_.running() && loading_) { - if (rom()->isLoaded()) { + if (loading_ && !memory_setup_) { + snes_.SetupMemory(*rom()); + memory_setup_ = true; + } + + if (rom()->isLoaded() && power_) { snes_.Init(*rom()); running_ = true; } @@ -38,6 +104,13 @@ void Emulator::Run() { RenderNavBar(); + ImGui::Button(ICON_MD_ARROW_FORWARD_IOS); + ImGui::SameLine(); + ImGui::Button(ICON_MD_DOUBLE_ARROW); + ImGui::SameLine(); + ImGui::Button(ICON_MD_SUBDIRECTORY_ARROW_RIGHT); + ImGui::SameLine(); + if (running_) { HandleEvents(); UpdateEmulator(); @@ -67,7 +140,9 @@ void Emulator::RenderNavBar() { if (ImGui::BeginMenu("Game")) { MENU_ITEM("Load ROM") { loading_ = true; } + MENU_ITEM("Power On") { power_ = true; } MENU_ITEM("Power Off") { + power_ = false; running_ = false; loading_ = false; debugger_ = false; @@ -133,6 +208,7 @@ void Emulator::RenderDebugger() { TableNextColumn(); RenderBreakpointList(); + DrawMemoryWindow(snes_.Memory()); ImGui::EndTable(); } }; diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index bad55335..77c9dc5a 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -39,9 +39,11 @@ class Emulator : public SharedROM { SNES snes_; + bool power_ = false; + bool loading_ = false; bool running_ = false; bool debugger_ = true; - bool loading_ = false; + bool memory_setup_ = false; bool integrated_debugger_mode_ = true; bool separate_debugger_mode_ = false; }; diff --git a/src/app/emu/memory/memory.h b/src/app/emu/memory/memory.h index 9a986d2c..ab4acbf6 100644 --- a/src/app/emu/memory/memory.h +++ b/src/app/emu/memory/memory.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "app/emu/debug/log.h" @@ -135,9 +136,21 @@ class Memory { virtual uint8_t at(int i) const = 0; }; +enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 }; + class MemoryImpl : public Memory, public Loggable { public: - void Initialize(const std::vector& romData) { + void Initialize(const std::vector& romData, + MemoryMapping mapping = MemoryMapping::SNES_LOROM) { + mapping_ = mapping; + if (mapping == MemoryMapping::PC_ADDRESS) { + memory_.resize(romData.size()); + std::copy(romData.begin(), romData.end(), memory_.begin()); + return; + } + + memory_.reserve(0x1000000); // 16 MB + const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB const size_t SRAM_SIZE = 0x10000; // 64 KB const size_t SYSTEM_RAM_SIZE = 0x20000; // 128 KB @@ -322,6 +335,7 @@ class MemoryImpl : public Memory, public Loggable { auto size() const { return memory_.size(); } auto begin() const { return memory_.begin(); } auto end() const { return memory_.end(); } + auto data() const { return memory_.data(); } // Define memory regions std::vector rom_; @@ -334,6 +348,10 @@ class MemoryImpl : public Memory, public Loggable { uint32_t bank = address >> 16; uint32_t offset = address & 0xFFFF; + if (mapping_ == MemoryMapping::PC_ADDRESS) { + return address; + } + if (bank <= 0x3F) { if (offset <= 0x1FFF) { return offset; // Shadow RAM @@ -364,10 +382,12 @@ class MemoryImpl : public Memory, public Loggable { std::vector observers_; // Memory (64KB) - std::array memory_; + std::vector memory_; // Stack Pointer uint16_t SP_ = 0x01FF; + + MemoryMapping mapping_ = MemoryMapping::SNES_LOROM; }; } // namespace emu diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index e7a25813..af26dc06 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -108,22 +108,15 @@ ROMInfo SNES::ReadRomHeader(uint32_t offset) { } void SNES::Init(ROM& rom) { - // Setup observers for the memory space - memory_.AddObserver(&apu); - memory_.AddObserver(&ppu); - - // Load the ROM into memory and set up the memory mapping - memory_.Initialize(rom.vector()); - - // Read the ROM header - auto header_offset = GetHeaderOffset(memory_); - rom_info_ = ReadRomHeader(header_offset); - // Perform a long jump into a FastROM bank (if the ROM speed is FastROM) // Disable the emulation flag (switch to 65816 native mode) // Initialize CPU cpu.Init(); + + // Read the ROM header + auto header_offset = GetHeaderOffset(memory_); + rom_info_ = ReadRomHeader(header_offset); cpu.PC = rom_info_.resetVector; // Initialize PPU diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index c9a0c62a..a218e0a3 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -65,12 +65,22 @@ class SNES : public DMA { auto Cpu() -> CPU& { return cpu; } auto Ppu() -> PPU& { return ppu; } + auto Memory() -> MemoryImpl* { return &memory_; } void SetCpuMode(int mode) { cpu_mode_ = mode; } CPU::UpdateMode GetCpuMode() const { return static_cast(cpu_mode_); } + void SetupMemory(ROM& rom) { + // Setup observers for the memory space + memory_.AddObserver(&apu); + memory_.AddObserver(&ppu); + + // Load the ROM into memory and set up the memory mapping + memory_.Initialize(rom.vector()); + } + private: void WriteToRegister(uint16_t address, uint8_t value) { memory_.WriteByte(address, value); diff --git a/src/app/gui/style.cc b/src/app/gui/style.cc index c90df451..c8d2819c 100644 --- a/src/app/gui/style.cc +++ b/src/app/gui/style.cc @@ -8,15 +8,376 @@ namespace app { namespace gui { -void TextWithSeparators(const absl::string_view &text) { +void DrawDisplaySettings(ImGuiStyle* ref) { + // You can pass in a reference ImGuiStyle structure to compare to, revert to + // and save to (without a reference style pointer, we will use one compared + // locally as a reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) ref_saved_style = style; + init = false; + if (ref == NULL) ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings (expose floating-pointer border sizes as boolean + // representing 0.0f or 1.0f) + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, + "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the + // same value as FrameRounding + { + bool border = (style.WindowBorderSize > 0.0f); + if (ImGui::Checkbox("WindowBorder", &border)) { + style.WindowBorderSize = border ? 1.0f : 0.0f; + } + } + ImGui::SameLine(); + { + bool border = (style.FrameBorderSize > 0.0f); + if (ImGui::Checkbox("FrameBorder", &border)) { + style.FrameBorderSize = border ? 1.0f : 0.0f; + } + } + ImGui::SameLine(); + { + bool border = (style.PopupBorderSize > 0.0f); + if (ImGui::Checkbox("PopupBorder", &border)) { + style.PopupBorderSize = border ? 1.0f : 0.0f; + } + } + + // Save/Revert button + if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) style = *ref; + ImGui::SameLine(); + + ImGui::Separator(); + + if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { + if (ImGui::BeginTabItem("Sizes")) { + ImGui::SeparatorText("Main"); + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, + 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, + 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, + "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, + "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, + "%.0f"); + + ImGui::SeparatorText("Borders"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, + 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, + 2.0f, "%.0f"); + + ImGui::SeparatorText("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, + 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, + "%.0f"); + + ImGui::SeparatorText("Tables"); + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderAngle("TableAngledHeadersAngle", + &style.TableAngledHeadersAngle, -50.0f, +50.0f); + + ImGui::SeparatorText("Widgets"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, + 0.0f, 1.0f, "%.2f"); + int window_menu_button_position = style.WindowMenuButtonPosition + 1; + if (ImGui::Combo("WindowMenuButtonPosition", + (int*)&window_menu_button_position, + "None\0Left\0Right\0")) + style.WindowMenuButtonPosition = window_menu_button_position - 1; + ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, + "Left\0Right\0"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, + 0.0f, 1.0f, "%.2f"); + ImGui::SameLine(); + + ImGui::SliderFloat2("SelectableTextAlign", + (float*)&style.SelectableTextAlign, 0.0f, 1.0f, + "%.2f"); + ImGui::SameLine(); + + ImGui::SliderFloat("SeparatorTextBorderSize", + &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("SeparatorTextAlign", + (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, + "%.2f"); + ImGui::SliderFloat2("SeparatorTextPadding", + (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, + "%.0f"); + ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, + 12.0f, "%.0f"); + + ImGui::SeparatorText("Tooltips"); + for (int n = 0; n < 2; n++) + if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" + : "HoverFlagsForTooltipNav")) { + ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse + : &style.HoverFlagsForTooltipNav; + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, + ImGuiHoveredFlags_DelayNone); + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, + ImGuiHoveredFlags_DelayShort); + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, + ImGuiHoveredFlags_DelayNormal); + ImGui::CheckboxFlags("ImGuiHoveredFlags_Stationary", p, + ImGuiHoveredFlags_Stationary); + ImGui::CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, + ImGuiHoveredFlags_NoSharedDelay); + ImGui::TreePop(); + } + + ImGui::SeparatorText("Misc"); + ImGui::SliderFloat2("DisplaySafeAreaPadding", + (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, + "%.0f"); + ImGui::SameLine(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Colors")) { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export")) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || + memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText( + "colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, " + "%.2ff);" IM_NEWLINE, + name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(120); + ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); + ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = 0; + if (ImGui::RadioButton("Opaque", + alpha_flags == ImGuiColorEditFlags_None)) { + alpha_flags = ImGuiColorEditFlags_None; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", + alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreview; + } + ImGui::SameLine(); + if (ImGui::RadioButton( + "Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; + } + ImGui::SameLine(); + + ImGui::SetNextWindowSizeConstraints( + ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), + ImVec2(FLT_MAX, FLT_MAX)); + ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border, + ImGuiWindowFlags_AlwaysVerticalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar | + ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], + ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { + // Tips: in a real user application, you may want to merge and use + // an icon font into the main font, so instead of "Save"/"Revert" + // you'd use icons! Read the FAQ and docs/FONTS.md about using icon + // fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + if (ImGui::Button("Save")) { + ref->Colors[i] = style.Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + if (ImGui::Button("Revert")) { + style.Colors[i] = ref->Colors[i]; + } + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Fonts")) { + ImGuiIO& io = ImGui::GetIO(); + ImFontAtlas* atlas = io.Fonts; + ImGui::ShowFontAtlas(atlas); + + // Post-baking font scaling. Note that this is NOT the nice way of + // scaling fonts, read below. (we enforce hard clamping manually as by + // default DragFloat/SliderFloat allows CTRL+Click text to get out of + // bounds). + const float MIN_SCALE = 0.3f; + const float MAX_SCALE = 2.0f; + + static float window_scale = 1.0f; + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + if (ImGui::DragFloat( + "window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, + "%.2f", + ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + ImGui::SetWindowFontScale(window_scale); + ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, + MAX_SCALE, "%.2f", + ImGuiSliderFlags_AlwaysClamp); // Scale everything + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Rendering")) { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + ImGui::SameLine(); + + ImGui::Checkbox("Anti-aliased lines use texture", + &style.AntiAliasedLinesUseTex); + ImGui::SameLine(); + + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragFloat("Curve Tessellation Tolerance", + &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, + "%.2f"); + if (style.CurveTessellationTol < 0.10f) + style.CurveTessellationTol = 0.10f; + + // When editing the "Circle Segment Max Error" value, draw a preview of + // its effect on auto-tessellated circles. + ImGui::DragFloat("Circle Tessellation Max Error", + &style.CircleTessellationMaxError, 0.005f, 0.10f, 5.0f, + "%.2f", ImGuiSliderFlags_AlwaysClamp); + const bool show_samples = ImGui::IsItemActive(); + if (show_samples) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + if (show_samples && ImGui::BeginTooltip()) { + ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::Spacing(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + for (int n = 0; n < 8; n++) { + const float RAD_MIN = 5.0f; + const float RAD_MAX = 70.0f; + const float rad = + RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); + + ImGui::BeginGroup(); + + ImGui::Text("R: %.f\nN: %d", rad, + draw_list->_CalcCircleAutoSegmentCount(rad)); + + const float canvas_width = std::max(min_widget_width, rad * 2.0f); + const float offset_x = floorf(canvas_width * 0.5f); + const float offset_y = floorf(RAD_MAX); + + const ImVec2 p1 = ImGui::GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, + ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + + /* + const ImVec2 p2 = ImGui::GetCursorScreenPos(); + draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), + rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + */ + + ImGui::EndGroup(); + ImGui::SameLine(); + } + ImGui::EndTooltip(); + } + ImGui::SameLine(); + + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, + "%.2f"); // Not exposing zero here so user doesn't + // "lose" the UI (zero alpha clips all + // widgets). But application code could have a + // toggle to switch between zero and non-zero. + ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, + 1.0f, "%.2f"); + ImGui::SameLine(); + + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopItemWidth(); +} + +void TextWithSeparators(const absl::string_view& text) { ImGui::Separator(); ImGui::Text("%s", text.data()); ImGui::Separator(); } void ColorsYaze() { - ImGuiStyle *style = &ImGui::GetStyle(); - ImVec4 *colors = style->Colors; + ImGuiStyle* style = &ImGui::GetStyle(); + ImVec4* colors = style->Colors; style->WindowPadding = ImVec2(10.f, 10.f); style->FramePadding = ImVec2(10.f, 2.f); diff --git a/src/app/gui/style.h b/src/app/gui/style.h index f27b774b..6599a0a1 100644 --- a/src/app/gui/style.h +++ b/src/app/gui/style.h @@ -9,6 +9,8 @@ namespace yaze { namespace app { namespace gui { +void DrawDisplaySettings(ImGuiStyle* ref = nullptr); + void TextWithSeparators(const absl::string_view& text); void ColorsYaze(); diff --git a/src/app/zelda3/dungeon/room_object.h b/src/app/zelda3/dungeon/room_object.h index 8166f239..7d2a1f47 100644 --- a/src/app/zelda3/dungeon/room_object.h +++ b/src/app/zelda3/dungeon/room_object.h @@ -23,72 +23,125 @@ class DungeonObjectRenderer : public SharedROM { public: struct PseudoVram { std::vector sheets; + // TODO: Initialize with mock VRAM data }; - void CreateVramFromRoomBlockset() { - // auto bitmap_manager = rom()->bitmap_manager(); - // uint16_t room_id = 0; - // auto room_blockset = rom()->room_blockset_ids[room_id]; - - // for (const auto blockset_id : room_blockset) { - // auto blockset = bitmap_manager[(uint16_t)blockset_id]; - // vram_.sheets.push_back(*blockset.get()); - // } + DungeonObjectRenderer() { + // TODO: Constructor implementation } - void RenderObjectsAsBitmaps() { - rom_data_ = rom()->vector(); - memory_.Initialize(rom_data_); - cpu.Init(); + void LoadObject(uint16_t objectId) { + // Prepare the CPU and memory environment + memory_.Initialize(rom()->vector()); - auto subtype1_ptr = core::subtype1_tiles; - auto subtype1_routine_ptr = core::subtype1_tiles + 0x200; - std::array routine_ptrs; - for (int i = 0; i < 256; i++) { - uint16_t actual_ptr = rom()->toint16(subtype1_routine_ptr + (i * 2)); - routine_ptrs[i] = actual_ptr; - std::cout << std::hex << routine_ptrs[i] << std::endl; + // Fetch the subtype pointers for the given object ID + auto subtypeInfo = FetchSubtypeInfo(objectId); + + // Configure the object based on the fetched information + ConfigureObject(subtypeInfo); + + // Run the CPU emulation for the object's draw routines + RenderObject(subtypeInfo); + } + + private: + struct SubtypeInfo { + uint16_t subtypePtr; + uint16_t routinePtr; + // Additional fields as needed + }; + + SubtypeInfo FetchSubtypeInfo(uint16_t objectId) { + SubtypeInfo info; + + // Determine the subtype based on objectId + // Assuming subtype is determined by some bits in objectId; modify as needed + uint8_t subtype = (objectId >> 8) & 0xFF; // Example: top 8 bits + + // Based on the subtype, fetch the correct pointers + switch (subtype) { + case 1: // Subtype 1 + info.subtypePtr = core::subtype1_tiles + (objectId & 0xFF) * 2; + info.routinePtr = core::subtype1_tiles + 0x200 + (objectId & 0xFF) * 2; + break; + case 2: // Subtype 2 + info.subtypePtr = core::subtype2_tiles + (objectId & 0x7F) * 2; + info.routinePtr = core::subtype2_tiles + 0x80 + (objectId & 0x7F) * 2; + break; + case 3: // Subtype 3 + info.subtypePtr = core::subtype3_tiles + (objectId & 0xFF) * 2; + info.routinePtr = core::subtype3_tiles + 0x100 + (objectId & 0xFF) * 2; + break; + default: + // Handle unknown subtype + throw std::runtime_error("Unknown subtype for object ID: " + + std::to_string(objectId)); } - int i = 0; - for (const auto routine_ptr : routine_ptrs) { - cpu.PC = routine_ptr - 2; - cpu.PB = 0x00; + // Convert pointers from ROM-relative to absolute (if necessary) + // info.subtypePtr = ConvertToAbsolutePtr(info.subtypePtr); + // info.routinePtr = ConvertToAbsolutePtr(info.routinePtr); - auto cycles_to_run = clock_.GetCycleCount(); + return info; + } - while (true) { - auto opcode = cpu.FetchByte(); - // Fetch and execute an instruction - cpu.ExecuteInstruction(opcode); + void ConfigureObject(const SubtypeInfo& info) { + // TODO: Use the information in info to set up the object's initial state + // This may include setting CPU registers, loading specific tiles into VRAM, + // etc. + } - // Handle any interrupts, if necessary - cpu.HandleInterrupts(); + void RenderObject(const SubtypeInfo& info) { + // Assuming that the routine pointer and other necessary setup is done in + // ConfigureObject Start CPU at the routine's entry point + cpu.PC = + info.routinePtr; // info should be a member or passed as a parameter + cpu.PB = 0x01; // Set the program bank; adjust based on your memory mapping - // Check if the instruction is RTS - if (opcode == 0x60) { - break; - } - i++; - if (i > 50) { - break; - } + // Run the CPU emulation loop + while (true) { + // Fetch the next opcode + uint8_t opcode = cpu.FetchByte(); + + // Execute the fetched instruction + cpu.ExecuteInstruction(opcode); + + // Handle any interrupts, if necessary + cpu.HandleInterrupts(); + + // Check if the end of the routine is reached (typically RTS instruction) + if (opcode == 0x60) { // RTS opcode + break; } + + // Additional checks can be added here, e.g., maximum cycles or + // instructions + + // Update the PPU state if necessary + // ppu.Update(); + + // After PPU update, reflect any changes in the Bitmap(s) + // UpdateBitmapFromPPU(); } - auto subtype2_ptr = core::subtype2_tiles; - auto subtype2_routine_ptr = - core::subtype2_tiles + 0x80; // Where the draw routines start - std::array subtype2_routine_ptrs; - for (int i = 0; i < 128; i++) { - subtype2_routine_ptrs[i] = subtype2_routine_ptr + i * 2; - } - - auto subtype3_ptr = core::subtype3_tiles; - auto subtype3_routine_ptr = - core::subtype3_tiles + 0x100; // Where the draw routines start + // Post-rendering operations (if any) + // PostRenderOperations(); } + // Helper function to update Bitmap from PPU state + void UpdateBitmapFromPPU() { + // TODO: Implement logic to transfer PPU state changes to the Bitmap + // This involves reading the tile data and other graphics info from PPU + // and rendering it to the Bitmap object + } + + // Optional: Handle any operations after rendering + void PostRenderOperations() { + // TODO: Implement any cleanup or additional processing needed after + // rendering + } + + // Members std::vector rom_data_; emu::MemoryImpl memory_; emu::ClockImpl clock_; @@ -98,6 +151,43 @@ class DungeonObjectRenderer : public SharedROM { PseudoVram vram_; }; +// void CreateVramFromRoomBlockset() { +// // auto bitmap_manager = rom()->bitmap_manager(); +// // uint16_t room_id = 0; +// // auto room_blockset = rom()->room_blockset_ids[room_id]; + +// // for (const auto blockset_id : room_blockset) { +// // auto blockset = bitmap_manager[(uint16_t)blockset_id]; +// // vram_.sheets.push_back(*blockset.get()); +// // } +// } + +// int i = 0; +// for (const auto routine_ptr : routine_ptrs) { +// cpu.PC = routine_ptr - 2; +// cpu.PB = 0x01; + +// auto cycles_to_run = clock_.GetCycleCount(); + +// while (true) { +// auto opcode = cpu.FetchByte(); +// // Fetch and execute an instruction +// cpu.ExecuteInstruction(opcode); + +// // Handle any interrupts, if necessary +// cpu.HandleInterrupts(); + +// // Check if the instruction is RTS +// if (opcode == 0x60) { +// break; +// } +// i++; +// if (i > 50) { +// break; +// } +// } +// } + enum class SpecialObjectType { Chest, BigChest, InterroomStairs }; struct Tile {};