[Scummvm-git-logs] scummvm master -> c360fbf218cbb009de6daac0d8e87bb6f9425934

sev- noreply at scummvm.org
Sun Apr 19 14:24:23 UTC 2026


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

Summary:
7e55f340c2 DIRECTOR: DT: Add Filmloop score viewer
c360fbf218 JANITORIAL: Move addThinRect to dt-internal.h to avoid duplication


Commit: 7e55f340c2c46d30021e632bdde016eea8e52ff7
    https://github.com/scummvm/scummvm/commit/7e55f340c2c46d30021e632bdde016eea8e52ff7
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-04-19T16:24:18+02:00

Commit Message:
DIRECTOR: DT: Add Filmloop score viewer

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-castdetails.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index ce8a089aff6..6d3cd7eb807 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -282,7 +282,7 @@ ImGuiImage getImageID(CastMember *castMember) {
 
 	bmpMember->load();
 	Picture *pic = bmpMember->_picture;
-	if (!pic)
+	if (!pic || !pic->_surface.getPixels())
 		return {};
 
 	ImTextureID textureID = (ImTextureID)(intptr_t)g_system->getImGuiTexture(pic->_surface, pic->_palette, pic->_paletteColors);
diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index 4ae6e758e35..9994701e4cd 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -166,8 +166,7 @@ void showCast() {
 						continue;
 
 					for (auto castMember : *cast->_loadedCast) {
-						if (!castMember._value->isLoaded())
-							continue;
+						castMember._value->load();
 
 						Common::String name(getDisplayName(castMember._value));
 						if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
@@ -185,6 +184,7 @@ void showCast() {
 							ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
 							ImVec2(0, 32.f) // match row height
 						)) {
+							castMember._value->load();
 							_state->_castDetails._castMember = castMember._value;
 							_state->_w.castDetails = true;
 						}
@@ -261,8 +261,7 @@ void showCast() {
 						continue;
 
 					for (auto castMember : *cast->_loadedCast) {
-						if (!castMember._value->isLoaded())
-							continue;
+						castMember._value->load();
 
 						Common::String name(getDisplayName(castMember._value));
 						if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
@@ -333,6 +332,7 @@ void showCast() {
 
 						if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
 							// Cast Member Clicked
+							castMember._value->load();
 							_state->_castDetails._castMember = castMember._value; // Must set _castMember before making the caseDetails window visible to prevent null castMember
 							_state->_w.castDetails = true;
 						}
diff --git a/engines/director/debugger/dt-castdetails.cpp b/engines/director/debugger/dt-castdetails.cpp
index d9aa8d3a04c..d3ef3463e12 100644
--- a/engines/director/debugger/dt-castdetails.cpp
+++ b/engines/director/debugger/dt-castdetails.cpp
@@ -31,6 +31,10 @@
 #include "director/castmember/richtext.h"
 #include "director/castmember/shape.h"
 #include "director/castmember/bitmap.h"
+#include "director/sprite.h"
+#include "director/castmember/filmloop.h"
+#include "director/frame.h"
+#include "director/score.h"
 
 #include "director/types.h"
 
@@ -498,6 +502,247 @@ void drawCastProps(Cast *cast) {
 	}
 }
 
+// Per filmloop curent frame, keyed by cast member pointer
+static Common::HashMap<FilmLoopCastMember *, int> _filmLoopCurrentFrame;
+
+void drawFilmLoopCMprops(FilmLoopCastMember *member) {
+	assert(member != nullptr);
+	if (ImGui::BeginTabItem("Film Loop")) {
+		if (ImGui::CollapsingHeader("Properties", ImGuiTreeNodeFlags_DefaultOpen)) {
+			if (ImGui::BeginTable("##FilmLoopProps", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
+				showPropertyBool("looping", member->_looping);
+				showPropertyBool("enableSound", member->_enableSound);
+				showPropertyBool("crop", member->_crop);
+				showPropertyBool("center", member->_center);
+				if (member->_score)
+					showProperty("frameCount", "%d", (int)member->_score->_scoreCache.size());
+				ImGui::EndTable();
+			}
+		}
+
+		Score *score = member->_score;
+		if (!score || score->_scoreCache.empty()) {
+			ImGui::TextDisabled("No frames loaded");
+			ImGui::EndTabItem();
+			return;
+		}
+
+		int numFrames = (int)score->_scoreCache.size();
+
+		// find highest active channel
+		int maxChannel = 0;
+		for (int f = 0; f < numFrames; f++) {
+			Frame *frame = score->_scoreCache[f];
+			if (!frame) continue;
+			for (int ch = 1; ch < (int)frame->_sprites.size(); ch++) {
+				if (frame->_sprites[ch] && frame->_sprites[ch]->_castId.member != 0)
+					maxChannel = MAX(maxChannel, ch);
+			}
+		}
+
+		if (maxChannel == 0) {
+			ImGui::TextDisabled("No sprite data");
+			ImGui::EndTabItem();
+			return;
+		}
+
+		// Initialize current frame for this member if needed
+		if (!_filmLoopCurrentFrame.contains(member))
+			_filmLoopCurrentFrame[member] = 0;
+		int &currentFrame = _filmLoopCurrentFrame[member];
+
+		const float cellW = 30.0f;
+		const float cellH = 18.0f;
+		const float labelW = 24.0f;
+		const float rulerH = 16.0f;
+		float gridW = labelW + numFrames * cellW;
+		float gridH = rulerH + maxChannel * cellH;
+
+		ImGui::Spacing();
+		ImGui::Text("Score");
+		ImGui::Separator();
+
+		// Frame navigation controls
+		if (ImGui::Button("|<")) currentFrame = 0;
+		ImGui::SameLine();
+		if (ImGui::Button("<") && currentFrame > 0) currentFrame--;
+		ImGui::SameLine();
+		ImGui::Text("Frame %d / %d", currentFrame + 1, numFrames);
+		ImGui::SameLine();
+		if (ImGui::Button(">") && currentFrame < numFrames - 1) currentFrame++;
+		ImGui::SameLine();
+		if (ImGui::Button(">|")) currentFrame = numFrames - 1;
+
+		// Frame preview, show thumbnails of all active sprites in the current frame
+		Frame *previewFrame = score->_scoreCache[currentFrame];
+		Cast *cast = member->getCast();
+		if (previewFrame && cast) {
+			const float thumbSize = 48.0f;
+			bool anySprite = false;
+			for (int ch = 1; ch <= maxChannel; ch++) {
+				if (ch >= (int)previewFrame->_sprites.size()) break;
+				Sprite *sp = previewFrame->_sprites[ch];
+				if (!sp || sp->_castId.member == 0) continue;
+
+				CastMember *cm = cast->getCastMember(sp->_castId.member, true);
+				if (!cm) continue;
+
+				if (!anySprite) {
+					ImGui::Separator();
+					ImGui::Text("Frame preview");
+					anySprite = true;
+				}
+
+				ImGuiImage imgID = getImageID(cm);
+				ImGui::BeginGroup();
+				if (imgID.id) {
+					showImage(imgID, cm->getName().c_str(), thumbSize);
+				} else {
+					// No image -> draw a placeholder box
+					ImVec2 pos = ImGui::GetCursorScreenPos();
+					ImGui::GetWindowDrawList()->AddRect(pos, ImVec2(pos.x + thumbSize, pos.y + thumbSize), _state->theme->borderColor);
+					ImGui::Dummy(ImVec2(thumbSize, thumbSize));
+				}
+				ImGui::Text("ch%d", ch);
+				ImGui::EndGroup();
+				ImGui::SameLine();
+			}
+			if (anySprite)
+				ImGui::NewLine();
+		}
+		ImGui::Separator();
+
+		// Scrollable child region
+		float scrollbarH = ImGui::GetStyle().ScrollbarSize;
+		ImGui::BeginChild("##FilmLoopScoreChild", ImVec2(0, MIN(gridH + scrollbarH + 4.0f, 200.0f)), false, ImGuiWindowFlags_HorizontalScrollbar);
+
+		ImDrawList *dl = ImGui::GetWindowDrawList();
+		ImVec2 origin = ImGui::GetCursorScreenPos();
+
+		// Ruler
+		for (int f = 0; f < numFrames; f++) {
+			float x = origin.x + labelW + f * cellW;
+			float y = origin.y;
+			ImVec2 rMin = ImVec2(x, y);
+			ImVec2 rMax = ImVec2(x + cellW, y + rulerH);
+			ImU32 rulerCol = ((f + 1) % 5 == 0) ? _state->theme->tableDarkColor : _state->theme->tableLightColor;
+			dl->AddRectFilled(rMin, rMax, rulerCol);
+			dl->AddLine(ImVec2(rMin.x, rMin.y), ImVec2(rMax.x, rMin.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(rMax.x, rMin.y), ImVec2(rMax.x, rMax.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(rMax.x, rMax.y), ImVec2(rMin.x, rMax.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(rMin.x, rMax.y), ImVec2(rMin.x, rMin.y), _state->theme->borderColor, 0.1f);
+			Common::String label = Common::String::format("%d", f + 1);
+			ImVec2 textSz = ImGui::CalcTextSize(label.c_str());
+			dl->AddText(ImVec2(x + (cellW - textSz.x) * 0.5f, y + (rulerH - textSz.y) * 0.5f), _state->theme->gridTextColor, label.c_str());
+		}
+
+		// Playhead
+		{
+			float px = origin.x + labelW + currentFrame * cellW;
+			dl->AddLine(ImVec2(px, origin.y), ImVec2(px, origin.y + gridH), _state->theme->playhead_color, 2.0f);
+			dl->AddTriangleFilled(
+				ImVec2(px - 5.0f, origin.y),
+				ImVec2(px + 5.0f, origin.y),
+				ImVec2(px, origin.y + 8.0f),
+				_state->theme->playhead_color);
+		}
+
+		// Channel rows
+		for (int ch = 1; ch <= maxChannel; ch++) {
+			float y = origin.y + rulerH + (ch - 1) * cellH;
+
+			// label cells
+			ImVec2 lblMin = ImVec2(origin.x, y);
+			ImVec2 lblMax = ImVec2(origin.x + labelW, y + cellH);
+			dl->AddRectFilled(lblMin, lblMax, _state->theme->tableDarkColor);
+			dl->AddLine(ImVec2(lblMin.x, lblMin.y), ImVec2(lblMax.x, lblMin.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(lblMax.x, lblMin.y), ImVec2(lblMax.x, lblMax.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(lblMax.x, lblMax.y), ImVec2(lblMin.x, lblMax.y), _state->theme->borderColor, 0.1f);
+			dl->AddLine(ImVec2(lblMin.x, lblMax.y), ImVec2(lblMin.x, lblMin.y), _state->theme->borderColor, 0.1f);
+			Common::String chLabel = Common::String::format("%d", ch);
+			ImVec2 chSz = ImGui::CalcTextSize(chLabel.c_str());
+			dl->AddText(ImVec2(origin.x + (labelW - chSz.x) * 0.5f, y + (cellH - chSz.y) * 0.5f), _state->theme->gridTextColor, chLabel.c_str());
+
+			// Pass 1: cell backgrounds
+			for (int f = 0; f < numFrames; f++) {
+				float x = origin.x + labelW + f * cellW;
+				ImVec2 cMin = ImVec2(x, y);
+				ImVec2 cMax = ImVec2(x + cellW, y + cellH);
+				ImU32 col = ((f + 1) % 5 == 0) ? _state->theme->tableDarkColor : _state->theme->tableLightColor;
+				dl->AddRectFilled(cMin, cMax, col);
+				dl->AddLine(ImVec2(cMin.x, cMin.y), ImVec2(cMax.x, cMin.y), _state->theme->borderColor, 0.1f);
+				dl->AddLine(ImVec2(cMax.x, cMin.y), ImVec2(cMax.x, cMax.y), _state->theme->borderColor, 0.1f);
+				dl->AddLine(ImVec2(cMax.x, cMax.y), ImVec2(cMin.x, cMax.y), _state->theme->borderColor, 0.1f);
+				dl->AddLine(ImVec2(cMin.x, cMax.y), ImVec2(cMin.x, cMin.y), _state->theme->borderColor, 0.1f);
+			}
+
+			// Pass 2: span bars
+			int f = 0;
+			while (f < numFrames) {
+				Frame *frame = score->_scoreCache[f];
+				if (!frame || ch >= (int)frame->_sprites.size() || !frame->_sprites[ch] || frame->_sprites[ch]->_castId.member == 0) {
+					f++;
+					continue;
+				}
+
+				// Find span end
+				int spanStart = f;
+				int memberNum = frame->_sprites[ch]->_castId.member;
+				int spanEnd = f;
+				while (spanEnd + 1 < numFrames) {
+					Frame *nf = score->_scoreCache[spanEnd + 1];
+					if (nf && ch < (int)nf->_sprites.size() && nf->_sprites[ch] && nf->_sprites[ch]->_castId.member == memberNum)
+						spanEnd++;
+					else
+						break;
+				}
+
+				float x1 = origin.x + labelW + spanStart * cellW;
+				float x2 = origin.x + labelW + (spanEnd + 1) * cellW;
+				float cy = y + cellH * 0.5f;
+				float pad = 1.0f;
+
+				int colorIdx = memberNum % 6;
+				ImU32 barColor = _state->theme->contColors[colorIdx];
+
+				dl->AddRectFilled(ImVec2(x1, y + pad), ImVec2(x2 - 1.0f, y + cellH - pad), barColor);
+				dl->AddLine(ImVec2(x1 + 6.0f, cy), ImVec2(x2 - 6.0f, cy), _state->theme->gridTextColor, 1.0f);
+				dl->AddCircle(ImVec2(x1 + 4.0f, cy), 3.0f, _state->theme->gridTextColor, 0, 1.5f);
+				dl->AddRect(ImVec2(x2 - 7.0f, cy - 3.0f), ImVec2(x2 - 1.0f, cy + 3.0f), _state->theme->gridTextColor, 0.0f, 0, 1.5f);
+
+				float spanW = x2 - x1 - 8.0f;
+				Common::String label = Common::String::format("%d", memberNum);
+				if (spanW > 4.0f) {
+					float textY = y + (cellH - ImGui::GetTextLineHeight()) * 0.5f;
+					// clipText function is static in dt-score.cpp, so inline a simple clip
+					Common::String clipped = label;
+					if (ImGui::CalcTextSize(clipped.c_str()).x > spanW) clipped = "";
+					if (!clipped.empty())
+						dl->AddText(ImVec2(x1 + 4.0f, textY), _state->theme->gridTextColor, clipped.c_str());
+				}
+
+				// Tooltip
+				ImGui::SetCursorScreenPos(ImVec2(x1, y));
+				ImGui::InvisibleButton(Common::String::format("##fl_%d_%d", ch, spanStart).c_str(), ImVec2(x2 - x1, cellH));
+				if (ImGui::IsItemHovered()) {
+					ImGui::BeginTooltip();
+					ImGui::Text("Channel %d | Frames %d-%d | Cast member %d", ch, spanStart + 1, spanEnd + 1, memberNum);
+					ImGui::EndTooltip();
+				}
+
+				f = spanEnd + 1;
+			}
+		}
+
+		// Advance the cursor
+		ImGui::SetCursorScreenPos(ImVec2(origin.x, origin.y + gridH));
+		ImGui::Dummy(ImVec2(gridW, 0));
+
+		ImGui::EndChild();
+		ImGui::EndTabItem();
+	}
+}
+
 void drawCMTypeProps(CastMember *member) {
 	assert(member != nullptr);
 	switch (member->_type) {
@@ -516,9 +761,11 @@ void drawCMTypeProps(CastMember *member) {
 	case kCastShape:
 		drawShapeCMprops(static_cast<ShapeCastMember *>(member));
 		break;
+	case kCastFilmLoop:
+		drawFilmLoopCMprops(static_cast<FilmLoopCastMember *>(member));
+		break;
 	case kCastTypeAny:
 	case kCastTypeNull:
-	case kCastFilmLoop:
 	case kCastPalette:
 	case kCastPicture:
 	case kCastSound:


Commit: c360fbf218cbb009de6daac0d8e87bb6f9425934
    https://github.com/scummvm/scummvm/commit/c360fbf218cbb009de6daac0d8e87bb6f9425934
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-04-19T16:24:18+02:00

Commit Message:
JANITORIAL: Move addThinRect to dt-internal.h to avoid duplication

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


diff --git a/engines/director/debugger/dt-castdetails.cpp b/engines/director/debugger/dt-castdetails.cpp
index d3ef3463e12..7a990fd2aa4 100644
--- a/engines/director/debugger/dt-castdetails.cpp
+++ b/engines/director/debugger/dt-castdetails.cpp
@@ -627,10 +627,7 @@ void drawFilmLoopCMprops(FilmLoopCastMember *member) {
 			ImVec2 rMax = ImVec2(x + cellW, y + rulerH);
 			ImU32 rulerCol = ((f + 1) % 5 == 0) ? _state->theme->tableDarkColor : _state->theme->tableLightColor;
 			dl->AddRectFilled(rMin, rMax, rulerCol);
-			dl->AddLine(ImVec2(rMin.x, rMin.y), ImVec2(rMax.x, rMin.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(rMax.x, rMin.y), ImVec2(rMax.x, rMax.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(rMax.x, rMax.y), ImVec2(rMin.x, rMax.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(rMin.x, rMax.y), ImVec2(rMin.x, rMin.y), _state->theme->borderColor, 0.1f);
+			addThinRect(dl, rMin, rMax, _state->theme->borderColor);
 			Common::String label = Common::String::format("%d", f + 1);
 			ImVec2 textSz = ImGui::CalcTextSize(label.c_str());
 			dl->AddText(ImVec2(x + (cellW - textSz.x) * 0.5f, y + (rulerH - textSz.y) * 0.5f), _state->theme->gridTextColor, label.c_str());
@@ -655,10 +652,7 @@ void drawFilmLoopCMprops(FilmLoopCastMember *member) {
 			ImVec2 lblMin = ImVec2(origin.x, y);
 			ImVec2 lblMax = ImVec2(origin.x + labelW, y + cellH);
 			dl->AddRectFilled(lblMin, lblMax, _state->theme->tableDarkColor);
-			dl->AddLine(ImVec2(lblMin.x, lblMin.y), ImVec2(lblMax.x, lblMin.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(lblMax.x, lblMin.y), ImVec2(lblMax.x, lblMax.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(lblMax.x, lblMax.y), ImVec2(lblMin.x, lblMax.y), _state->theme->borderColor, 0.1f);
-			dl->AddLine(ImVec2(lblMin.x, lblMax.y), ImVec2(lblMin.x, lblMin.y), _state->theme->borderColor, 0.1f);
+			addThinRect(dl, lblMin, lblMax, _state->theme->borderColor);
 			Common::String chLabel = Common::String::format("%d", ch);
 			ImVec2 chSz = ImGui::CalcTextSize(chLabel.c_str());
 			dl->AddText(ImVec2(origin.x + (labelW - chSz.x) * 0.5f, y + (cellH - chSz.y) * 0.5f), _state->theme->gridTextColor, chLabel.c_str());
@@ -670,10 +664,7 @@ void drawFilmLoopCMprops(FilmLoopCastMember *member) {
 				ImVec2 cMax = ImVec2(x + cellW, y + cellH);
 				ImU32 col = ((f + 1) % 5 == 0) ? _state->theme->tableDarkColor : _state->theme->tableLightColor;
 				dl->AddRectFilled(cMin, cMax, col);
-				dl->AddLine(ImVec2(cMin.x, cMin.y), ImVec2(cMax.x, cMin.y), _state->theme->borderColor, 0.1f);
-				dl->AddLine(ImVec2(cMax.x, cMin.y), ImVec2(cMax.x, cMax.y), _state->theme->borderColor, 0.1f);
-				dl->AddLine(ImVec2(cMax.x, cMax.y), ImVec2(cMin.x, cMax.y), _state->theme->borderColor, 0.1f);
-				dl->AddLine(ImVec2(cMin.x, cMax.y), ImVec2(cMin.x, cMin.y), _state->theme->borderColor, 0.1f);
+				addThinRect(dl, cMin, cMax, _state->theme->borderColor);
 			}
 
 			// Pass 2: span bars
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index 1f008597947..91abb490c6a 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -335,6 +335,14 @@ Window *windowListCombo(Common::String *target);
 Common::String formatHandlerName(int scriptId, int castId, Common::String handlerName, ScriptType scriptType, bool childScript);
 void setTheme(int themeIndex);
 
+// helper to draw thin rectangles for table grid
+inline void addThinRect(ImDrawList *dl, ImVec2 min, ImVec2 max, ImU32 col, float thickness = 0.1f) {
+	dl->AddLine(ImVec2(min.x, min.y), ImVec2(max.x, min.y), col, thickness); // top
+	dl->AddLine(ImVec2(max.x, min.y), ImVec2(max.x, max.y), col, thickness); // right
+	dl->AddLine(ImVec2(max.x, max.y), ImVec2(min.x, max.y), col, thickness); // bottom
+	dl->AddLine(ImVec2(min.x, max.y), ImVec2(min.x, min.y), col, thickness); // left
+}
+
 void showCast();		// dt-cast.cpp
 void showCastDetails();	// dt-castdetails.cpp
 void showControlPanel();// dt-controlpanel.cpp
diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index 831864766b4..7933e870505 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -98,13 +98,7 @@ static inline ImU32 U32(const ImVec4 &c) {
 	return ImGui::GetColorU32(c);
 }
 
-// helper to draw thin rectangles for table grid
-static void addThinRect(ImDrawList *dl, ImVec2 min, ImVec2 max, ImU32 col, float thickness = 0.1f) {
-	dl->AddLine(ImVec2(min.x, min.y), ImVec2(max.x, min.y), col, thickness); // top
-	dl->AddLine(ImVec2(max.x, min.y), ImVec2(max.x, max.y), col, thickness); // right
-	dl->AddLine(ImVec2(max.x, max.y), ImVec2(min.x, max.y), col, thickness); // bottom
-	dl->AddLine(ImVec2(min.x, max.y), ImVec2(min.x, min.y), col, thickness); // left
-}
+
 
 // help to draw tool tip with wrapped text
 static void setTooltip(const char *fmt, ...) {




More information about the Scummvm-git-logs mailing list