Skip to content

Commit ff7cff0

Browse files
Spruill-1Copilot
andcommitted
Properties panel: dont rebuild while user is mid-edit
The 4 Hz binding-value refresh in OnRenderTick was unconditionally calling UpdatePropertiesPanel() whenever the selected node had any property binding and the graph had dirty nodes -- which is every single 250 ms tick on a Clock-driven graph. UpdatePropertiesPanel calls panel.Children().Clear() then rebuilds the entire control tree from scratch, which destroys whatever XAML control had keyboard focus. Symptom: with a complex graph + playing Clock + a node with bindings selected, clicking into a TextBox / NumberBox / ComboBox to edit a property had a < 250 ms window before the rebuild blew away the focused control. Effective lag on first successful edit: pseudorandom 0..20+ seconds depending on click timing. Fix: walk the focused elements parent chain via VisualTreeHelper and skip the periodic rebuild while any descendant of PropertiesPanel holds focus. The user-driven rebuild paths (selection change, property mutation) are unaffected -- they always rebuild. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1e91a8a commit ff7cff0

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

MainWindow.RenderTick.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,18 @@ namespace winrt::ShaderLab::implementation
247247
// every frame) caused a 4 Hz rebuild of the entire properties
248248
// panel -- visibly jittering Slider/NumberBox widths as auto-sized
249249
// controls re-laid out.
250+
//
251+
// Additional guard: skip the rebuild when any descendant of the
252+
// PropertiesPanel currently has keyboard focus (i.e. the user
253+
// is mid-edit in a TextBox / NumberBox / dropdown). The clear()
254+
// + recreate cycle was destroying the focused control on every
255+
// 250 ms tick, making any property whose owner has a bound
256+
// sibling effectively un-editable in a Clock-driven graph.
250257
if (m_selectedNodeId != 0 && m_graph.HasDirtyNodes())
251258
{
252259
auto* selNode = m_graph.FindNode(m_selectedNodeId);
253-
if (selNode && !selNode->propertyBindings.empty())
260+
if (selNode && !selNode->propertyBindings.empty() &&
261+
!IsPropertiesPanelInteracting())
254262
UpdatePropertiesPanel();
255263
}
256264
// Refresh MCP activity indicator (dot color fade + tooltip "Xs ago"

MainWindow.xaml.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2644,6 +2644,31 @@ namespace winrt::ShaderLab::implementation
26442644
args.Handled(true);
26452645
}
26462646

2647+
bool MainWindow::IsPropertiesPanelInteracting()
2648+
{
2649+
// Walks the focused element's parent chain looking for the
2650+
// PropertiesPanel StackPanel. If found, the user is mid-edit on
2651+
// some control inside the panel and a Clear() + recreate would
2652+
// destroy their in-progress input. Used by the 4 Hz binding-value
2653+
// refresh on Clock-driven graphs.
2654+
try
2655+
{
2656+
auto root = this->Content().XamlRoot();
2657+
if (!root) return false;
2658+
auto focused = winrt::Microsoft::UI::Xaml::Input::FocusManager::GetFocusedElement(root);
2659+
auto cur = focused.try_as<winrt::Microsoft::UI::Xaml::DependencyObject>();
2660+
auto target = PropertiesPanel().try_as<winrt::Microsoft::UI::Xaml::DependencyObject>();
2661+
if (!cur || !target) return false;
2662+
for (int hops = 0; hops < 64 && cur; ++hops)
2663+
{
2664+
if (cur == target) return true;
2665+
cur = winrt::Microsoft::UI::Xaml::Media::VisualTreeHelper::GetParent(cur);
2666+
}
2667+
}
2668+
catch (...) {}
2669+
return false;
2670+
}
2671+
26472672
void MainWindow::UpdatePropertiesPanel()
26482673
{
26492674
namespace Controls = winrt::Microsoft::UI::Xaml::Controls;

MainWindow.xaml.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ namespace winrt::ShaderLab::implementation
342342
D2D1_POINT_2F m_graphPanStart{};
343343
D2D1_POINT_2F m_graphPanOrigin{};
344344
void UpdatePropertiesPanel();
345+
// True when any descendant of PropertiesPanel currently has keyboard
346+
// focus (TextBox cursor, NumberBox edit, dropdown open). Used by the
347+
// 4 Hz binding-value refresh path to avoid clobbering an in-progress
348+
// edit by Clear() + recreate on the panel.
349+
bool IsPropertiesPanelInteracting();
345350
void ShowCurveEditorDialog(uint32_t nodeId, const std::wstring& propertyKey, std::function<void()> markDirty);
346351
void AddMathExpressionInput(uint32_t nodeId);
347352
void RemoveMathExpressionInput(uint32_t nodeId, const std::wstring& paramName);

0 commit comments

Comments
 (0)