252 ImGui::TextColored(theme.accent_color,
"%s SRAM Viewer",
256 ImGui::SameLine(ImGui::GetWindowWidth() - 100);
258 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 2.0f);
259 ImVec4 connected_color = ImVec4(0.1f, pulse, 0.3f, 1.0f);
262 ImGui::TextColored(theme.status_error,
"%s Disconnected",
ICON_MD_ERROR);
268 ImGui::TextDisabled(
"Socket");
269 const char* preview =
271 selected_socket_index_ < static_cast<int>(
socket_paths_.size()))
273 :
"No sockets found";
274 ImGui::SetNextItemWidth(-40);
275 if (ImGui::BeginCombo(
"##sram_socket_combo", preview)) {
276 for (
int i = 0; i < static_cast<int>(
socket_paths_.size()); ++i) {
284 ImGui::SetItemDefaultFocus();
294 ImGui::TextDisabled(
"Path");
295 ImGui::SetNextItemWidth(-1);
296 ImGui::InputTextWithHint(
"##sram_socket_path",
"/tmp/mesen2-12345.sock",
327 ImGui::SetNextItemWidth(60);
340 ImGui::TextColored(theme.text_secondary_color,
"%s",
347 std::vector<const core::SramVariable*> story_vars;
348 std::vector<const core::SramVariable*> dungeon_vars;
349 std::vector<const core::SramVariable*> item_vars;
350 std::vector<const core::SramVariable*> other_vars;
352 std::string filter_lower;
355 std::transform(filter_lower.begin(), filter_lower.end(),
356 filter_lower.begin(), ::tolower);
361 if (!filter_lower.empty()) {
362 std::string name_lower = var.name;
363 std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(),
365 std::string purpose_lower = var.purpose;
366 std::transform(purpose_lower.begin(), purpose_lower.end(),
367 purpose_lower.begin(), ::tolower);
368 std::string addr_str = absl::StrFormat(
"$%06X", var.address);
369 std::string addr_lower = addr_str;
370 std::transform(addr_lower.begin(), addr_lower.end(), addr_lower.begin(),
373 if (name_lower.find(filter_lower) == std::string::npos &&
374 purpose_lower.find(filter_lower) == std::string::npos &&
375 addr_lower.find(filter_lower) == std::string::npos) {
380 if (IsInStoryRange(var.address)) {
381 story_vars.push_back(&var);
382 }
else if (IsDungeonAddress(var.address)) {
383 dungeon_vars.push_back(&var);
384 }
else if (IsInItemsRange(var.address)) {
385 item_vars.push_back(&var);
387 other_vars.push_back(&var);
392 if (!story_vars.empty()) {
393 std::string story_label =
396 if (ImGui::CollapsingHeader(
400 for (
const auto* var : story_vars) {
409 if (!dungeon_vars.empty()) {
410 std::string dungeon_label =
412 dungeon_vars.size());
413 if (ImGui::CollapsingHeader(
414 dungeon_label.c_str(),
417 for (
const auto* var : dungeon_vars) {
426 if (!item_vars.empty()) {
427 std::string items_label =
430 if (ImGui::CollapsingHeader(
434 for (
const auto* var : item_vars) {
443 if (!other_vars.empty()) {
444 std::string other_label =
447 if (ImGui::CollapsingHeader(other_label.c_str())) {
448 for (
const auto* var : other_vars) {
457 float current_time =
static_cast<float>(ImGui::GetTime());
459 ImGui::PushID(
static_cast<int>(var.
address));
462 bool recently_changed =
false;
465 float elapsed = current_time - ts_it->second;
466 if (elapsed < kChangeHighlightDuration) {
467 recently_changed =
true;
469 float alpha = 1.0f - (elapsed / kChangeHighlightDuration);
470 ImVec4 highlight_color = ImVec4(0.9f, 0.8f, 0.1f, alpha * 0.3f);
471 ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
472 ImVec2 row_size = ImVec2(ImGui::GetContentRegionAvail().x,
473 ImGui::GetTextLineHeightWithSpacing());
474 ImGui::GetWindowDrawList()->AddRectFilled(
475 cursor_pos, ImVec2(cursor_pos.x + row_size.x,
476 cursor_pos.y + row_size.y),
477 ImGui::ColorConvertFloat4ToU32(highlight_color));
482 ImGui::TextColored(theme.text_secondary_color,
"$%06X", var.
address);
486 if (recently_changed) {
487 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.2f, 1.0f),
"%s",
490 ImGui::Text(
"%s", var.
name.c_str());
494 if (!var.
purpose.empty() && ImGui::IsItemHovered()) {
495 ImGui::SetTooltip(
"%s", var.
purpose.c_str());
501 uint8_t value = val_it->second;
504 ImGui::SameLine(240);
505 ImGui::Text(
"%d", value);
508 ImGui::SameLine(290);
509 ImGui::TextColored(theme.text_secondary_color,
"$%02X", value);
512 ImGui::SameLine(340);
513 std::string edit_label = absl::StrFormat(
515 if (ImGui::SmallButton(edit_label.c_str())) {
519 ImGui::OpenPopup(
"SramEditPopup");
525 if (var.
address == kDungeonCrystals) {
527 }
else if (var.
address == kStoryRangeStart) {
538 bool any_changed =
false;
539 uint8_t new_value = value;
541 for (
const auto& bit : kCrystalBits) {
542 bool set = (value & bit.mask) != 0;
543 std::string cb_label = absl::StrFormat(
"%s##crystal_%02X", bit.label,
545 if (ImGui::Checkbox(cb_label.c_str(), &set)) {
547 new_value |= bit.mask;
549 new_value &= ~bit.mask;
565 int current_index = value;
566 if (current_index >= kGameStateLabelCount) {
570 const char* preview = (current_index >= 0 && current_index < kGameStateLabelCount)
571 ? kGameStateLabels[current_index]
574 ImGui::SetNextItemWidth(200);
575 if (ImGui::BeginCombo(
"##gamestate_combo", preview)) {
576 for (
int i = 0; i < kGameStateLabelCount; ++i) {
577 bool selected = (i == current_index);
578 if (ImGui::Selectable(kGameStateLabels[i], selected)) {
579 PokeValue(address,
static_cast<uint8_t
>(i));
582 ImGui::SetItemDefaultFocus();