[Scummvm-git-logs] scummvm master -> 317a1c8ddc3f740bfd985bac0332cde8e71515aa

sev- noreply at scummvm.org
Thu Jul 16 12:59:12 UTC 2026


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
76dfd720e4 DIRECTOR: DT: Show the execution point when navigating the call stack
f368817d50 DIRECTOR: Fix Cast Details resolution and clip long cast lists in DT
d968721c84 DIRECTOR: DT: Add serial numbers and a member count to the Cast window
317a1c8ddc DIRECTOR: DT: Composite debugger redraw requests before drawing


Commit: 76dfd720e4472d83449ce0039a654fd382eb509c
    https://github.com/scummvm/scummvm/commit/76dfd720e4472d83449ce0039a654fd382eb509c
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-16T14:59:06+02:00

Commit Message:
DIRECTOR: DT: Show the execution point when navigating the call stack

Clicking a stack frame now marks the calling statement. The frame's
return address points after the call, so the call instruction is found
in the disassembly and its raw offset translated to the engine's pc,
which the renderer uses to highlight the containing statement.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-script-d2.cpp
    engines/director/debugger/dt-script-d4.cpp
    engines/director/debugger/dt-scripts.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 9599022ca7d..3d7c488a88e 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -615,7 +615,11 @@ void setScriptToDisplay(const ImGuiScript &script) {
 	uint index = scriptData->_scripts.size();
 	scriptData->_scrollToCurrent = true;
 	if (index && scriptData->_scripts[index - 1] == script) {
+		// operator== ignores pc, so carry the new execution point over
+		scriptData->_scripts[index - 1].pc = script.pc;
+		scriptData->_current = index - 1;
 		scriptData->_showScript = true;
+		_state->_dbg._scrollToPC = true;
 		return;
 	}
 	scriptData->_scripts.push_back(script);
diff --git a/engines/director/debugger/dt-script-d2.cpp b/engines/director/debugger/dt-script-d2.cpp
index fe15024e6df..8323ce34a69 100644
--- a/engines/director/debugger/dt-script-d2.cpp
+++ b/engines/director/debugger/dt-script-d2.cpp
@@ -41,8 +41,10 @@ private:
 	bool _isScriptInDebug = false;
 	bool _currentStatementDisplayed = false;
 	bool _scrollTo = false;
-	bool _scrollDone = true;
+	bool _scrollDone = false;
 	int _renderLineID = 1;
+	uint _markPc = 0;
+	bool _markPcValid = false;
 
 public:
 	explicit RenderOldScriptVisitor(ImGuiScript &script, bool scrollTo) : _script(script), _scrollTo(scrollTo) {
@@ -52,6 +54,15 @@ public:
 			if (head->sp.ctx)
 				_isScriptInDebug = (head->sp.ctx->_id == script.id.member) && (*head->sp.name == script.handlerId);
 		}
+		// find the statement containing the pc from the previous render's offsets
+		if (_script.pc != 0) {
+			for (uint off : _script.startOffsets) {
+				if (off <= _script.pc && (!_markPcValid || off > _markPc)) {
+					_markPc = off;
+					_markPcValid = true;
+				}
+			}
+		}
 		_script.startOffsets.clear();
 	}
 
@@ -787,7 +798,7 @@ private:
 		bool showCurrentStatement = false;
 		_script.startOffsets.push_back(pc);
 
-		if (_script.pc != 0 && pc >= _script.pc) {
+		if (_script.pc != 0 && (_markPcValid ? pc == _markPc : pc >= _script.pc)) {
 			if (!_currentStatementDisplayed) {
 				showCurrentStatement = true;
 				_currentStatementDisplayed = true;
@@ -855,7 +866,7 @@ private:
 		if (showCurrentStatement) {
 			dl->AddQuadFilled(ImVec2(pos.x, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 10.f), ImVec2(pos.x, pos.y + 10.f), ImColor(_state->theme->current_statement));
 			dl->AddTriangleFilled(ImVec2(pos.x + 8.f, pos.y), ImVec2(pos.x + 14.f, pos.y + 7.f), ImVec2(pos.x + 8.f, pos.y + 14.f), ImColor(_state->theme->current_statement));
-			if (!_scrollDone && _scrollTo && g_lingo->_state->callstack.size() != _state->_dbg._callstackSize) {
+			if (!_scrollDone && _scrollTo) {
 				ImGui::SetScrollHereY(0.5f);
 				_scrollDone = true;
 			}
diff --git a/engines/director/debugger/dt-script-d4.cpp b/engines/director/debugger/dt-script-d4.cpp
index f621236ca29..5b8d1ed3f9b 100644
--- a/engines/director/debugger/dt-script-d4.cpp
+++ b/engines/director/debugger/dt-script-d4.cpp
@@ -44,6 +44,15 @@ public:
 			if (head->sp.ctx)
 				_isScriptInDebug = (head->sp.ctx->_id == script.id.member) && (*head->sp.name == script.handlerId);
 		}
+		// find the statement containing the pc from the previous render's offsets
+		if (_script.pc != 0) {
+			for (uint off : _script.startOffsets) {
+				if (off <= _script.pc && (!_markPcValid || off > _markPc)) {
+					_markPc = off;
+					_markPcValid = true;
+				}
+			}
+		}
 		_script.startOffsets.clear();
 	}
 
@@ -1137,7 +1146,7 @@ private:
 		uint pc = _script.byteOffsets[p];
 		_script.startOffsets.push_back(pc);
 
-		if (_script.pc != 0 && pc >= _script.pc) {
+		if (_script.pc != 0 && (_markPcValid ? pc == _markPc : pc >= _script.pc)) {
 			if (!_currentStatementDisplayed) {
 				showCurrentStatement = true;
 				_currentStatementDisplayed = true;
@@ -1205,7 +1214,7 @@ private:
 		if (showCurrentStatement) {
 			dl->AddQuadFilled(ImVec2(pos.x, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 10.f), ImVec2(pos.x, pos.y + 10.f), ImColor(_state->theme->current_statement));
 			dl->AddTriangleFilled(ImVec2(pos.x + 8.f, pos.y), ImVec2(pos.x + 14.f, pos.y + 7.f), ImVec2(pos.x + 8.f, pos.y + 14.f), ImColor(_state->theme->current_statement));
-			if (_state->_dbg._scrollToPC && _scrollTo && g_lingo->_state->callstack.size() != _state->_dbg._callstackSize) {
+			if (_state->_dbg._scrollToPC && _scrollTo) {
 				ImGui::SetScrollHereY(0.5f);
 				_state->_dbg._scrollToPC = false;
 			}
@@ -1254,6 +1263,8 @@ private:
 	bool _currentStatementDisplayed = false;
 	bool _isScriptInDebug = false;
 	int _renderLineID = 1;
+	uint _markPc = 0;
+	bool _markPcValid = false;
 	bool _scrollTo = false;
 };
 
diff --git a/engines/director/debugger/dt-scripts.cpp b/engines/director/debugger/dt-scripts.cpp
index cdf05b21ad2..74ab7ec8862 100644
--- a/engines/director/debugger/dt-scripts.cpp
+++ b/engines/director/debugger/dt-scripts.cpp
@@ -109,7 +109,21 @@ static void renderCallStack(uint pc) {
 			Common::String moviePath = movie->getArchive()->getPathName().toString();
 
 			ImGuiScript script = buildImGuiHandlerScript(scriptContext, castLibID, *head->sp.name, moviePath);
+			// retPC points after the call, find the call instruction in the disassembly
 			script.pc = framePc;
+			if (i > 0 && framePc > 0) {
+				// fallback for scripts without a disassembly
+				script.pc = framePc - 1;
+				for (auto &bytecode : script.bytecodeArray) {
+					if (bytecode.pos >= script.byteOffsets.size())
+						break;
+					// bytecode positions are raw offsets, translate to the engine's pc
+					uint32 bytecodePc = script.byteOffsets[bytecode.pos];
+					if (bytecodePc >= framePc)
+						break;
+					script.pc = bytecodePc;
+				}
+			}
 			setScriptToDisplay(script);
 		}
 	}


Commit: f368817d50ea4f6e4fc197efd09aaa9bee48a1c2
    https://github.com/scummvm/scummvm/commit/f368817d50ea4f6e4fc197efd09aaa9bee48a1c2
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-16T14:59:06+02:00

Commit Message:
DIRECTOR: Fix Cast Details resolution and clip long cast lists in DT

Cast Details resolved the clicked member against the current movie, so
a member from another window's movie could not be shown; it now
resolves against the window it was opened from. The cast list is
rendered through a clipper so only the visible rows are submitted.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-castdetails.cpp
    engines/director/debugger/dt-internal.h
    engines/director/debugger/dt-score.cpp
    engines/director/movie.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 3d7c488a88e..9fe41264ca6 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -674,6 +674,20 @@ ImColor brightenColor(const ImColor& color, float factor) {
 	return ImColor(col);
 }
 
+Window *findWindowByName(const Common::String &name) {
+	if (name.empty())
+		return nullptr;
+	Movie *stageMovie = g_director->getStage()->getCurrentMovie();
+	if (stageMovie && stageMovie->getMacName() == name)
+		return g_director->getStage();
+	for (auto window : *g_director->getWindowList()) {
+		Movie *movie = window->getCurrentMovie();
+		if (movie && movie->getMacName() == name)
+			return window;
+	}
+	return nullptr;
+}
+
 Window *windowListCombo(Common::String *target) {
 	const Common::Array<Window *> *windowList = g_director->getWindowList();
 	const Common::String selWin = *target;
diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index 07f588ca6c4..e3044115f6f 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -111,174 +111,149 @@ Common::String getDisplayName(CastMember *castMember) {
 	return Common::String::format("%u", castMember->getID());
 }
 
-void drawCastRow(Cast* cast) {
-	assert(cast);
-	// member numbers repeat across cast libs, so scope the row IDs
-	ImGui::PushID(cast->_castLibID);
-	for (auto castMember : *cast->_loadedCast) {
-		castMember._value->load();
+struct CastRowEntry {
+	const Cast *cast = nullptr;
+	CastMember *member = nullptr;
+	int id = 0;
+	Common::String name;
+};
+
+// Collects the cast's members that pass the filters, sorted by member number.
+static void gatherCastMembers(const Cast *cast, Common::Array<CastRowEntry> &rows) {
+	if (!cast || !cast->_loadedCast)
+		return;
+
+	Common::Array<int> ids;
+	for (auto &it : *cast->_loadedCast)
+		ids.push_back(it._key);
+	Common::sort(ids.begin(), ids.end());
+
+	for (int id : ids) {
+		CastMember *member = cast->_loadedCast->getVal(id);
+		member->load();
 
-		Common::String name(getDisplayName(castMember._value));
+		Common::String name(getDisplayName(member));
 		if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
 			continue;
-		if ((castMember._value->_type != kCastTypeAny) &&
-				!(_state->_cast._typeFilter & (1 << (int)castMember._value->_type)))
+		if ((member->_type != kCastTypeAny) &&
+				!(_state->_cast._typeFilter & (1 << (int)member->_type)))
 			continue;
 
-		ImGui::TableNextRow();
+		CastRowEntry entry;
+		entry.cast = cast;
+		entry.member = member;
+		entry.id = id;
+		entry.name = name;
+		rows.push_back(entry);
+	}
+}
 
-		// Make the entire row selectable/clickable
-		ImGui::TableSetColumnIndex(0);
-		if (ImGui::Selectable(
-			Common::String::format("##row%d", castMember._key).c_str(),
-			false,
+static ImGuiImage getThumbnail(CastMember *member) {
+	switch (member->_type) {
+	case kCastBitmap:
+		return getImageID(member);
+	case kCastText:
+	case kCastRichText:
+	case kCastButton:
+		return getTextID(member);
+	case kCastShape:
+		return getShapeID(member);
+	default:
+		return {};
+	}
+}
+
+static void drawCastRow(const CastRowEntry &entry) {
+	// member numbers repeat across cast libs, so scope the row IDs
+	ImGui::PushID(entry.cast->_castLibID);
+	ImGui::PushID(entry.id);
+
+	ImGui::TableNextRow();
+
+	// Make the entire row selectable/clickable
+	ImGui::TableSetColumnIndex(0);
+	if (ImGui::Selectable("##row", false,
 			ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
 			ImVec2(0, 32.f) // match row height
-		)) {
-			castMember._value->load();
-			_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
-			_state->_w.castDetails = true;
-		}
-		ImGui::SameLine();
+	)) {
+		_state->_castDetails._castMemberID = CastMemberID(entry.id, entry.cast->_castLibID);
+		_state->_castDetails._window = _state->_castWindow;
+		_state->_w.castDetails = true;
+	}
+	ImGui::SameLine();
 
-		ImGui::Text("%s %s", toIcon(castMember._value->_type), name.c_str());
+	ImGui::Text("%s %s", toIcon(entry.member->_type), entry.name.c_str());
 
-		ImGui::TableNextColumn();
-		ImGui::Text("%d", castMember._key);
+	ImGui::TableNextColumn();
+	ImGui::Text("%d", entry.id);
 
-		ImGui::TableNextColumn();
-		if (castMember._value->_type == CastType::kCastLingoScript) {
-			ScriptCastMember *scriptMember = (ScriptCastMember *)castMember._value;
-			ImGui::Text("%s", toString(scriptMember->_scriptType));
-		}
-		ImGui::TableNextColumn();
-		ImGui::Text("%s", toString(castMember._value->_type));
-
-		ImGui::TableNextColumn();
-		float columnWidth = ImGui::GetColumnWidth();
-
-		ImGuiImage imgID = {};
-		switch (castMember._value->_type) {
-		case kCastBitmap:
-			{
-				imgID = getImageID(castMember._value);
-				if (imgID.id) {
-					float offsetX = (columnWidth - 32.f) * 0.5f;
-					ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
-					showImage(imgID, name.c_str(), 32.f);
-				}
-			}
-			break;
-
-		case kCastText:
-		case kCastRichText:
-		case kCastButton:
-			{
-				imgID = getTextID(castMember._value);
-				if (imgID.id) {
-					float offsetX = (columnWidth - 32.f) * 0.5f;
-					ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
-					showImage(imgID, name.c_str(), 32.f);
-				}
-			}
-			break;
-
-		case kCastShape:
-			{
-				imgID = getShapeID(castMember._value);
-				if (imgID.id) {
-					float offsetX = (columnWidth - 32.f) * 0.5f;
-					ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
-					showImage(imgID, name.c_str(), 32.f);
-				}
-			}
-			break;
-		default:
-			break;
-		}
+	ImGui::TableNextColumn();
+	if (entry.member->_type == CastType::kCastLingoScript) {
+		ScriptCastMember *scriptMember = (ScriptCastMember *)entry.member;
+		ImGui::Text("%s", toString(scriptMember->_scriptType));
 	}
-	ImGui::PopID();
-}
+	ImGui::TableNextColumn();
+	ImGui::Text("%s", toString(entry.member->_type));
 
-static void drawCastGrid(const Cast *cast, float thumbnailSize) {
-	ImGui::PushID(cast->_castLibID);
-	for (auto castMember : *cast->_loadedCast) {
-		castMember._value->load();
+	ImGui::TableNextColumn();
+	float columnWidth = ImGui::GetColumnWidth();
 
-		Common::String name(getDisplayName(castMember._value));
-		if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
-			continue;
-		if ((castMember._value->_type != kCastTypeAny) && !(_state->_cast._typeFilter & (1 << (int)castMember._value->_type)))
-			continue;
-
-		ImGui::TableNextColumn();
+	ImGuiImage imgID = getThumbnail(entry.member);
+	if (imgID.id) {
+		float offsetX = (columnWidth - 32.f) * 0.5f;
+		ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
+		showImage(imgID, entry.name.c_str(), 32.f);
+	}
 
-		ImGui::BeginGroup();
-		const ImVec2 textSize = ImGui::CalcTextSize(name.c_str());
-		float textWidth = textSize.x;
-		float textHeight = textSize.y;
-		if (textWidth > thumbnailSize) {
-			textWidth = thumbnailSize;
-			textHeight *= (textSize.x / textWidth);
-		}
+	ImGui::PopID();
+	ImGui::PopID();
+}
 
-		ImGuiImage imgID = {};
-		switch (castMember._value->_type) {
-		case kCastBitmap:
-			{
-				imgID = getImageID(castMember._value);
-				if (imgID.id) {
-					showImage(imgID, name.c_str(), thumbnailSize);
-				}
-			}
-			break;
-
-		case kCastText:
-		case kCastRichText:
-		case kCastButton:
-			{
-				imgID = getTextID(castMember._value);
-				if (imgID.id) {
-					showImage(imgID, name.c_str(), thumbnailSize);
-				}
-			}
-			break;
+static void drawCastTile(const CastRowEntry &entry, float thumbnailSize) {
+	// member numbers repeat across cast libs, so scope the tile IDs
+	ImGui::PushID(entry.cast->_castLibID);
+	ImGui::PushID(entry.id);
+
+	// show the member number so tiles can be identified without clicking
+	Common::String label = Common::String::format("%d: %s", entry.id, entry.name.c_str());
+
+	ImGui::BeginGroup();
+	const ImVec2 textSize = ImGui::CalcTextSize(entry.name.c_str());
+	float textWidth = textSize.x;
+	float textHeight = textSize.y;
+	if (textWidth > thumbnailSize) {
+		textWidth = thumbnailSize;
+		textHeight *= (textSize.x / textWidth);
+	}
 
-		case kCastShape:
-			{
-				imgID = getShapeID(castMember._value);
-				if (imgID.id) {
-					showImage(imgID, name.c_str(), thumbnailSize);
-				}
-			}
-			break;
-		default:
-			break;
-		}
+	ImGuiImage imgID = getThumbnail(entry.member);
+	if (imgID.id) {
+		showImage(imgID, label.c_str(), thumbnailSize);
+	} else {
+		ImGui::InvisibleButton("##canvas", ImVec2(thumbnailSize, thumbnailSize));
+		const ImVec2 p0 = ImGui::GetItemRectMin();
+		const ImVec2 p1 = ImGui::GetItemRectMax();
+		ImGui::PushClipRect(p0, p1, true);
+		ImDrawList *draw_list = ImGui::GetWindowDrawList();
+		draw_list->AddRect(p0, p1, _state->theme->borderColor);
+		const ImVec2 pos = p0 + ImVec2((thumbnailSize - textWidth) * 0.5f, (thumbnailSize - textHeight) * 0.5f);
+		draw_list->AddText(nullptr, 0.f, pos, _state->theme->gridTextColor, entry.name.c_str(), 0, thumbnailSize);
+		draw_list->AddText(nullptr, 0.f, p1 - ImVec2(16, 16), _state->theme->gridTextColor, toIcon(entry.member->_type));
+		ImGui::PopClipRect();
+	}
+	ImGui::EndGroup();
 
-		if (!imgID.id) {
-			ImGui::PushID(castMember._key);
-			ImGui::InvisibleButton("##canvas", ImVec2(thumbnailSize, thumbnailSize));
-			ImGui::PopID();
-			const ImVec2 p0 = ImGui::GetItemRectMin();
-			const ImVec2 p1 = ImGui::GetItemRectMax();
-			ImGui::PushClipRect(p0, p1, true);
-			ImDrawList *draw_list = ImGui::GetWindowDrawList();
-			draw_list->AddRect(p0, p1, _state->theme->borderColor);
-			const ImVec2 pos = p0 + ImVec2((thumbnailSize - textWidth) * 0.5f, (thumbnailSize - textHeight) * 0.5f);
-			draw_list->AddText(nullptr, 0.f, pos, _state->theme->gridTextColor, name.c_str(), 0, thumbnailSize);
-			draw_list->AddText(nullptr, 0.f, p1 - ImVec2(16, 16), _state->theme->gridTextColor, toIcon(castMember._value->_type));
-			ImGui::PopClipRect();
-		}
-		ImGui::EndGroup();
+	if (!imgID.id)
+		ImGui::SetItemTooltip("%s", label.c_str());
 
-		if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
-			// Cast Member Clicked
-			castMember._value->load();
-			_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
-			_state->_w.castDetails = true;
-		}
+	if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
+		// Cast Member Clicked
+		_state->_castDetails._castMemberID = CastMemberID(entry.id, entry.cast->_castLibID);
+		_state->_castDetails._window = _state->_castWindow;
+		_state->_w.castDetails = true;
 	}
+
+	ImGui::PopID();
 	ImGui::PopID();
 }
 
@@ -326,6 +301,12 @@ void showCast() {
 		const ImVec2 childsize = ImGui::GetContentRegionAvail();
 		Movie *movie = selectedWindow->getCurrentMovie();
 		ImGui::BeginChild("##cast", ImVec2(childsize.x, childsize.y - sliderHeight));
+
+		Common::Array<CastRowEntry> rows;
+		for (auto it : *movie->getCasts())
+			gatherCastMembers(it._value, rows);
+		gatherCastMembers(movie->getSharedCast(), rows);
+
 		if (_state->_cast._listView) {
 			if (ImGui::BeginTable("Resources", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
 				ImGui::TableSetupColumn("Name", 0, 120.f);
@@ -335,17 +316,14 @@ void showCast() {
 				ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 50.f);
 				ImGui::TableHeadersRow();
 
-				for (auto it : *movie->getCasts()) {
-					Cast *cast = it._value;
-					if (!cast->_loadedCast)
-						continue;
-					drawCastRow(cast);
+				// only submit the visible rows, not the whole cast, each frame
+				ImGuiListClipper clipper;
+				clipper.Begin((int)rows.size());
+				while (clipper.Step()) {
+					for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+						drawCastRow(rows[i]);
 				}
 
-				Cast *sharedCast = movie->getSharedCast();
-				if(sharedCast && sharedCast->_loadedCast)
-					drawCastRow(sharedCast);
-
 				ImGui::EndTable();
 			}
 		} else {
@@ -354,17 +332,21 @@ void showCast() {
 			int columns = contentWidth / (thumbnailSize + 8.f);
 			columns = columns < 1 ? 1 : columns;
 			if (ImGui::BeginTable("Cast", columns)) {
-				for (auto it : *movie->getCasts()) {
-					const Cast *cast = it._value;
-					if (!cast->_loadedCast)
-						continue;
-					drawCastGrid(cast, thumbnailSize);
+				ImGuiListClipper clipper;
+				clipper.Begin(((int)rows.size() + columns - 1) / columns);
+				while (clipper.Step()) {
+					for (int r = clipper.DisplayStart; r < clipper.DisplayEnd; r++) {
+						ImGui::TableNextRow();
+						for (int c = 0; c < columns; c++) {
+							int index = r * columns + c;
+							if (index >= (int)rows.size())
+								break;
+							ImGui::TableNextColumn();
+							drawCastTile(rows[index], thumbnailSize);
+						}
+					}
 				}
 
-				const Cast *sharedCast = movie->getSharedCast();
-				if (sharedCast && sharedCast->_loadedCast)
-					drawCastGrid(sharedCast, thumbnailSize);
-
 				ImGui::EndTable();
 			}
 		}
diff --git a/engines/director/debugger/dt-castdetails.cpp b/engines/director/debugger/dt-castdetails.cpp
index 77f306b762a..bdf1be36075 100644
--- a/engines/director/debugger/dt-castdetails.cpp
+++ b/engines/director/debugger/dt-castdetails.cpp
@@ -1095,7 +1095,9 @@ void showCastDetails() {
 	ImGui::SetNextWindowSize(ImVec2(240, 480), ImGuiCond_FirstUseEver);
 
 	if (ImGui::Begin("Cast Details", &_state->_w.castDetails)) {
-		Movie *movie = g_director->getCurrentMovie();
+		// resolve in the movie of the window the member was opened from
+		Window *sourceWindow = findWindowByName(_state->_castDetails._window);
+		Movie *movie = sourceWindow ? sourceWindow->getCurrentMovie() : g_director->getCurrentMovie();
 		CastMember *member = movie ? movie->getCastMember(_state->_castDetails._castMemberID) : nullptr;
 		if (!member) {
 			ImGui::TextDisabled("No cast member selected");
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index 3b7f7d22086..876bfe35db8 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -232,6 +232,8 @@ typedef struct ImGuiState {
 	struct {
 		// stored as an ID: raw CastMember pointers dangle on movie switch
 		CastMemberID _castMemberID;
+		// name of the window whose movie owns the member
+		Common::String _window;
 		Common::HashMap<CastMemberID, int> _filmLoopCurrentFrame;
 	} _castDetails;
 
@@ -362,6 +364,7 @@ ImVec4 convertColor(uint32 color);
 void displayVariable(const Common::String &name, bool changed, bool outOfScope = false);
 ImColor brightenColor(const ImColor &color, float factor);
 Window *windowListCombo(Common::String *target);
+Window *findWindowByName(const Common::String &name);
 bool selectableViewButton(const char *label, bool selected);
 Common::String formatHandlerName(int scriptId, int castId, Common::String handlerName, ScriptType scriptType, bool childScript);
 void setTheme(int themeIndex);
diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index fa57a20c3d7..93c43d673f7 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -769,6 +769,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Window
 						CastMember *clickedCM = movie->getCastMember(sprite._castId);
 						if (clickedCM) {
 							_state->_castDetails._castMemberID = CastMemberID(clickedCM->getID(), clickedCM->getCast()->_castLibID);
+							_state->_castDetails._window = _state->_scoreWindow;
 							_state->_w.castDetails = true;
 						}
 					}
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 6ab5c351c71..f1a8fc925c0 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -567,6 +567,11 @@ bool Movie::loadCastLibFrom(uint16 libId, Common::Path &filename) {
 
 CastMember *Movie::getCastMember(CastMemberID memberID) {
 	CastMember *result = nullptr;
+	if (memberID.castLib == SHARED_CAST_LIB) {
+		if (_sharedCast)
+			result = _sharedCast->getCastMember(memberID.member);
+		return result;
+	}
 	if (_casts.contains(memberID.castLib)) {
 		if (memberID.member == 0)
 			return nullptr;


Commit: d968721c84c557ea29282f0cefbb514d94c0fc35
    https://github.com/scummvm/scummvm/commit/d968721c84c557ea29282f0cefbb514d94c0fc35
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-16T14:59:06+02:00

Commit Message:
DIRECTOR: DT: Add serial numbers and a member count to the Cast window

The list view leads with a serial column giving each member its
1-based position, alongside the cast member ID. The grid view gains a
toggle that overlays the member number on each tile, and the toolbar
shows the total member count (or shown/total when a filter is active).

Changed paths:
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-internal.h


diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index e3044115f6f..f52e09cec18 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -163,16 +163,16 @@ static ImGuiImage getThumbnail(CastMember *member) {
 	}
 }
 
-static void drawCastRow(const CastRowEntry &entry) {
+static void drawCastRow(const CastRowEntry &entry, int serial) {
 	// member numbers repeat across cast libs, so scope the row IDs
 	ImGui::PushID(entry.cast->_castLibID);
 	ImGui::PushID(entry.id);
 
 	ImGui::TableNextRow();
 
-	// Make the entire row selectable/clickable
+	// serial number, the 1-based position in the whole cast listing
 	ImGui::TableSetColumnIndex(0);
-	if (ImGui::Selectable("##row", false,
+	if (ImGui::Selectable(Common::String::format("%d", serial).c_str(), false,
 			ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
 			ImVec2(0, 32.f) // match row height
 	)) {
@@ -180,8 +180,8 @@ static void drawCastRow(const CastRowEntry &entry) {
 		_state->_castDetails._window = _state->_castWindow;
 		_state->_w.castDetails = true;
 	}
-	ImGui::SameLine();
 
+	ImGui::TableNextColumn();
 	ImGui::Text("%s %s", toIcon(entry.member->_type), entry.name.c_str());
 
 	ImGui::TableNextColumn();
@@ -209,13 +209,13 @@ static void drawCastRow(const CastRowEntry &entry) {
 	ImGui::PopID();
 }
 
-static void drawCastTile(const CastRowEntry &entry, float thumbnailSize) {
+static void drawCastTile(const CastRowEntry &entry, int serial, float thumbnailSize) {
 	// member numbers repeat across cast libs, so scope the tile IDs
 	ImGui::PushID(entry.cast->_castLibID);
 	ImGui::PushID(entry.id);
 
 	// show the member number so tiles can be identified without clicking
-	Common::String label = Common::String::format("%d: %s", entry.id, entry.name.c_str());
+	Common::String label = Common::String::format("No. %d, %d: %s", serial, entry.id, entry.name.c_str());
 
 	ImGui::BeginGroup();
 	const ImVec2 textSize = ImGui::CalcTextSize(entry.name.c_str());
@@ -243,6 +243,16 @@ static void drawCastTile(const CastRowEntry &entry, float thumbnailSize) {
 	}
 	ImGui::EndGroup();
 
+	// optional member-number overlay in the top-left corner of the tile
+	if (_state->_cast._showGridNumbers) {
+		const ImVec2 p0 = ImGui::GetItemRectMin();
+		Common::String num = Common::String::format("%d", entry.id);
+		const ImVec2 sz = ImGui::CalcTextSize(num.c_str());
+		ImDrawList *draw_list = ImGui::GetWindowDrawList();
+		draw_list->AddRectFilled(p0, p0 + sz + ImVec2(4.f, 2.f), IM_COL32(0, 0, 0, 160));
+		draw_list->AddText(p0 + ImVec2(2.f, 1.f), _state->theme->gridTextColor, num.c_str());
+	}
+
 	if (!imgID.id)
 		ImGui::SetItemTooltip("%s", label.c_str());
 
@@ -266,6 +276,12 @@ void showCast() {
 
 	if (ImGui::Begin("Cast", &_state->_w.cast)) {
 		Window *selectedWindow = windowListCombo(&_state->_castWindow);
+		Movie *movie = selectedWindow->getCurrentMovie();
+		if (!movie) {
+			ImGui::Text("No movie loaded");
+			ImGui::End();
+			return;
+		}
 
 		// display a toolbar with: grid/list/filters buttons + name filter
 		if (selectableViewButton(ICON_MS_LIST, _state->_cast._listView))
@@ -277,6 +293,13 @@ void showCast() {
 		ImGui::SetItemTooltip("Grid");
 		ImGui::SameLine();
 
+		// number overlay toggle, only meaningful in the grid view
+		if (!_state->_cast._listView) {
+			ImGuiEx::toggleButton(ICON_MS_123, &_state->_cast._showGridNumbers);
+			ImGui::SetItemTooltip("Show member numbers");
+			ImGui::SameLine();
+		}
+
 		if (ImGui::Button(ICON_MS_FILTER_ALT)) {
 			ImGui::OpenPopup("filters_popup");
 		}
@@ -294,23 +317,35 @@ void showCast() {
 			ImGui::EndPopup();
 		}
 		_state->_cast._nameFilter.Draw();
+
+		Common::Array<CastRowEntry> rows;
+		int total = 0;
+		for (auto it : *movie->getCasts()) {
+			gatherCastMembers(it._value, rows);
+			if (it._value->_loadedCast)
+				total += it._value->_loadedCast->size();
+		}
+		gatherCastMembers(movie->getSharedCast(), rows);
+		if (movie->getSharedCast() && movie->getSharedCast()->_loadedCast)
+			total += movie->getSharedCast()->_loadedCast->size();
+
+		ImGui::SameLine();
+		if ((int)rows.size() == total)
+			ImGui::Text("%d members", total);
+		else
+			ImGui::Text("%d / %d members", (int)rows.size(), total);
 		ImGui::Separator();
 
 		// display a list or a grid
 		const float sliderHeight = _state->_cast._listView ? 0.f : 38.f;
 		const ImVec2 childsize = ImGui::GetContentRegionAvail();
-		Movie *movie = selectedWindow->getCurrentMovie();
 		ImGui::BeginChild("##cast", ImVec2(childsize.x, childsize.y - sliderHeight));
 
-		Common::Array<CastRowEntry> rows;
-		for (auto it : *movie->getCasts())
-			gatherCastMembers(it._value, rows);
-		gatherCastMembers(movie->getSharedCast(), rows);
-
 		if (_state->_cast._listView) {
-			if (ImGui::BeginTable("Resources", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
+			if (ImGui::BeginTable("Resources", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
+				ImGui::TableSetupColumn("No.", 0, 30.f);
 				ImGui::TableSetupColumn("Name", 0, 120.f);
-				ImGui::TableSetupColumn("#", 0, 20.f);
+				ImGui::TableSetupColumn("ID", 0, 20.f);
 				ImGui::TableSetupColumn("Script", 0, 80.f);
 				ImGui::TableSetupColumn("Type", 0, 80.f);
 				ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 50.f);
@@ -321,7 +356,7 @@ void showCast() {
 				clipper.Begin((int)rows.size());
 				while (clipper.Step()) {
 					for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
-						drawCastRow(rows[i]);
+						drawCastRow(rows[i], i + 1);
 				}
 
 				ImGui::EndTable();
@@ -342,7 +377,7 @@ void showCast() {
 							if (index >= (int)rows.size())
 								break;
 							ImGui::TableNextColumn();
-							drawCastTile(rows[index], thumbnailSize);
+							drawCastTile(rows[index], index + 1, thumbnailSize);
 						}
 					}
 				}
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index 876bfe35db8..5c8ae45c02b 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -219,6 +219,7 @@ typedef struct ImGuiState {
 	struct {
 		Common::HashMap<CastMember *, ImGuiImage> _textures;
 		bool _listView = true;
+		bool _showGridNumbers = false;
 		int _thumbnailSize = 64;
 		ImGuiTextFilter _nameFilter;
 		int _typeFilter = 0xFFFF;


Commit: 317a1c8ddc3f740bfd985bac0332cde8e71515aa
    https://github.com/scummvm/scummvm/commit/317a1c8ddc3f740bfd985bac0332cde8e71515aa
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-16T14:59:06+02:00

Commit Message:
DIRECTOR: DT: Composite debugger redraw requests before drawing

The debugger's _windowToRedraw was serviced in onImGuiRender(), which
runs after the frame is already composited, so a forced redraw never
reached the screen while paused. Draining it in the main loop before
draw() makes it composite the same frame, so it is visible while the
movie is paused via the control panel.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/debugtools.h
    engines/director/director.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 9fe41264ca6..ff1d78da300 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -1017,11 +1017,6 @@ void onImGuiRender() {
 
 	invalidateStaleCaches();
 
-	if (_state->_windowToRedraw) {
-		_state->_windowToRedraw->render(true);
-		_state->_windowToRedraw = nullptr;
-	}
-
 	ImGuiIO &io = ImGui::GetIO();
 	io.ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
 
@@ -1126,6 +1121,13 @@ void onImGuiCleanup() {
 	_state = nullptr;
 }
 
+void renderPendingWindow() {
+	if (!_state || !_state->_windowToRedraw)
+		return;
+	_state->_windowToRedraw->render(true);
+	_state->_windowToRedraw = nullptr;
+}
+
 int getSelectedChannel(){
 	return _state ? _state->_selectedChannel : -1;
 }
diff --git a/engines/director/debugger/debugtools.h b/engines/director/debugger/debugtools.h
index dd554659144..ca1882bfd3a 100644
--- a/engines/director/debugger/debugtools.h
+++ b/engines/director/debugger/debugtools.h
@@ -27,6 +27,9 @@ namespace DT {
 void onImGuiInit();
 void onImGuiRender();
 void onImGuiCleanup();
+// Render a window the debugger asked to refresh. Called from the main loop
+// before compositing so a forced redraw is visible even while paused.
+void renderPendingWindow();
 int getSelectedChannel();
 void setSelectedChannel(int channel);
 bool isMouseInputIgnored();
diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 580e1072a76..9095f86f20d 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -252,6 +252,7 @@ void DirectorEngine::setVersion(uint16 version) {
 namespace DT {
 bool isMouseInputIgnored() { return false; }
 void setSelectedChannel(int channel) { }
+void renderPendingWindow() { }
 }
 #endif
 
@@ -378,6 +379,10 @@ Common::Error DirectorEngine::run() {
 			}
 		}
 
+		// service a debugger-requested redraw before compositing, so it
+		// is visible even when playback is paused and step() renders nothing
+		DT::renderPendingWindow();
+
 		draw();
 		while (!_windowsToForget.empty()) {
 			Window *window = _windowsToForget.back();




More information about the Scummvm-git-logs mailing list