@@ -37,6 +37,10 @@ Component::Component (StringRef componentID)
3737
3838Component::~Component ()
3939{
40+ #if YUP_ENABLE_COMPONENT_PAINT_PROFILING
41+ setPaintProfilingEnabled (false );
42+ #endif
43+
4044 if (options.onDesktop )
4145 removeFromDesktop ();
4246
@@ -902,6 +906,21 @@ void Component::setWantsKeyboardFocus (bool wantsFocus)
902906 options.wantsKeyboardFocus = wantsFocus;
903907}
904908
909+ bool Component::getWantsKeyboardFocus () const
910+ {
911+ return options.wantsKeyboardFocus ;
912+ }
913+
914+ void Component::setClickingGrabFocus (bool shouldGrabFocus)
915+ {
916+ options.clickingDoesNotGrabFocus = ! shouldGrabFocus;
917+ }
918+
919+ bool Component::getClickingGrabFocus () const
920+ {
921+ return ! options.clickingDoesNotGrabFocus ;
922+ }
923+
905924void Component::takeKeyboardFocus ()
906925{
907926 if (! options.wantsKeyboardFocus )
@@ -937,6 +956,20 @@ void Component::focusLost() {}
937956
938957// ==============================================================================
939958
959+ void Component::handleKeyboardFocusFromClick ()
960+ {
961+ for (auto * component = this ; component != nullptr ; component = component->parentComponent )
962+ {
963+ if (component->options .wantsKeyboardFocus && ! component->options .clickingDoesNotGrabFocus )
964+ {
965+ component->takeKeyboardFocus ();
966+ return ;
967+ }
968+ }
969+ }
970+
971+ // ==============================================================================
972+
940973NamedValueSet& Component::getProperties ()
941974{
942975 return properties;
@@ -1123,6 +1156,141 @@ bool Component::hasOpaqueChildCoveringArea (const Rectangle<float>& area)
11231156 return false ;
11241157}
11251158
1159+ #if YUP_ENABLE_COMPONENT_PAINT_PROFILING
1160+
1161+ // ==============================================================================
1162+
1163+ void Component::setPaintProfilingEnabled (bool shouldBeEnabled, PaintProfileOptions options)
1164+ {
1165+ if (shouldBeEnabled)
1166+ {
1167+ if (paintProfileStats == nullptr
1168+ || paintProfileStats->getCapacity () != options.sampleCapacity )
1169+ {
1170+ paintProfileStats = std::make_unique<PaintProfileStats> (options);
1171+ }
1172+ PaintProfiler::getInstance ().registerComponent (*this , *paintProfileStats);
1173+ }
1174+ else
1175+ {
1176+ PaintProfiler::getInstance ().deregisterComponent (*this );
1177+ paintProfileStats.reset ();
1178+ }
1179+ }
1180+
1181+ bool Component::isPaintProfilingEnabled () const
1182+ {
1183+ return paintProfileStats != nullptr ;
1184+ }
1185+
1186+ void Component::resetPaintProfiling ()
1187+ {
1188+ if (paintProfileStats != nullptr )
1189+ paintProfileStats->reset ();
1190+ }
1191+
1192+ PaintProfileStats* Component::getPaintProfileStats ()
1193+ {
1194+ return paintProfileStats.get ();
1195+ }
1196+
1197+ const PaintProfileStats* Component::getPaintProfileStats () const
1198+ {
1199+ return paintProfileStats.get ();
1200+ }
1201+
1202+ String Component::getPaintProfileName () const
1203+ {
1204+ if (componentTitle.isNotEmpty ())
1205+ return componentTitle;
1206+
1207+ if (componentID.isNotEmpty ())
1208+ return componentID;
1209+
1210+ return " Component" ;
1211+ }
1212+
1213+ // ==============================================================================
1214+
1215+ struct PaintProfileScopeEntry
1216+ {
1217+ double childrenMicros = 0.0 ;
1218+ };
1219+
1220+ thread_local std::vector<PaintProfileScopeEntry> paintProfileScopeStack;
1221+ static std::atomic<uint64> globalPaintIndexCounter { 0 };
1222+
1223+ class PaintProfileScope
1224+ {
1225+ public:
1226+ PaintProfileScope (Component& component,
1227+ const Rectangle<float >& repaintArea,
1228+ bool renderContinuous,
1229+ uint64 frameIndex)
1230+ : component (component)
1231+ , sample()
1232+ , totalStartMicros (ticksToMicros (Time::getHighResolutionTicks()))
1233+ , selfStartMicros (0.0 )
1234+ {
1235+ sample.frameIndex = frameIndex;
1236+ sample.paintIndex = globalPaintIndexCounter.fetch_add (1 , std::memory_order_relaxed);
1237+ sample.repaintArea = repaintArea;
1238+ sample.componentBounds = component.getBoundsRelativeToTopLevelComponent ().to <float >();
1239+ sample.renderContinuous = renderContinuous;
1240+
1241+ paintProfileScopeStack.push_back ({});
1242+ }
1243+
1244+ ~PaintProfileScope ()
1245+ {
1246+ const double totalEndMicros = ticksToMicros (Time::getHighResolutionTicks ());
1247+ sample.totalMicros = totalEndMicros - totalStartMicros;
1248+ sample.childrenMicros = paintProfileScopeStack.back ().childrenMicros ;
1249+ paintProfileScopeStack.pop_back ();
1250+
1251+ sample.frameworkMicros = std::max (0.0 ,
1252+ sample.totalMicros
1253+ - sample.selfMicros
1254+ - sample.childrenMicros );
1255+
1256+ if (auto * stats = component.getPaintProfileStats ())
1257+ stats->recordSample (sample);
1258+
1259+ if (! paintProfileScopeStack.empty ())
1260+ paintProfileScopeStack.back ().childrenMicros += sample.totalMicros ;
1261+ }
1262+
1263+ void beginSelf ()
1264+ {
1265+ selfStartMicros = ticksToMicros (Time::getHighResolutionTicks ());
1266+ }
1267+
1268+ void endSelf ()
1269+ {
1270+ sample.selfMicros += ticksToMicros (Time::getHighResolutionTicks ()) - selfStartMicros;
1271+ }
1272+
1273+ void markSelfPaintSkipped ()
1274+ {
1275+ sample.selfPaintSkipped = true ;
1276+ }
1277+
1278+ private:
1279+ static double ticksToMicros (int64 ticks)
1280+ {
1281+ return Time::highResolutionTicksToSeconds (ticks) * 1.0e6 ;
1282+ }
1283+
1284+ Component& component;
1285+ PaintProfileSample sample;
1286+ double totalStartMicros;
1287+ double selfStartMicros;
1288+
1289+ YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PaintProfileScope)
1290+ };
1291+
1292+ #endif // YUP_ENABLE_COMPONENT_PAINT_PROFILING
1293+
11261294// ==============================================================================
11271295
11281296void Component::internalRefreshDisplay (double lastFrameTimeSeconds)
@@ -1173,6 +1341,51 @@ void Component::internalPaint (Graphics& g, const Rectangle<float>& repaintArea,
11731341
11741342 options.isRepainting = true ;
11751343
1344+ #if YUP_ENABLE_COMPONENT_PAINT_PROFILING
1345+ const bool profilingActive = PaintProfiler::getInstance ().isEnabled ()
1346+ && paintProfileStats != nullptr ;
1347+
1348+ if (profilingActive)
1349+ {
1350+ const auto frameIndex = PaintProfiler::getInstance ().getCurrentFrameIndex ();
1351+ PaintProfileScope profileScope (*this , repaintArea, renderContinuous, frameIndex);
1352+
1353+ {
1354+ const auto globalState = g.saveState ();
1355+
1356+ g.setOpacity (opacity);
1357+ g.setDrawingArea (bounds);
1358+ if (! options.unclippedRendering )
1359+ g.setClipPath (boundsToRedraw);
1360+
1361+ g.setTransform (transform);
1362+
1363+ bool canSkipPaint = false ;
1364+ if (! options.unclippedRendering && ! isTransformed ())
1365+ canSkipPaint = hasOpaqueChildCoveringArea (boundsToRedraw);
1366+
1367+ if (! canSkipPaint)
1368+ {
1369+ const auto paintState = g.saveState ();
1370+ profileScope.beginSelf ();
1371+ paint (g);
1372+ profileScope.endSelf ();
1373+ }
1374+ else
1375+ {
1376+ profileScope.markSelfPaintSkipped ();
1377+ }
1378+
1379+ for (auto child : children)
1380+ child->internalPaint (g, boundsToRedraw, renderContinuous);
1381+
1382+ profileScope.beginSelf ();
1383+ paintOverChildren (g);
1384+ profileScope.endSelf ();
1385+ }
1386+ }
1387+ else
1388+ #endif
11761389 {
11771390 const auto globalState = g.saveState ();
11781391
@@ -1202,7 +1415,7 @@ void Component::internalPaint (Graphics& g, const Rectangle<float>& repaintArea,
12021415
12031416 options.isRepainting = false ;
12041417
1205- #if YUP_ENABLE_COMPONENT_REPAINT_DEBUGGING
1418+ #if YUP_ENABLE_COMPONENT_PAINT_DEBUGGING
12061419 g.setFillColor (debugColor);
12071420 g.setOpacity (0 .2f );
12081421 g.fillAll ();
@@ -1264,6 +1477,11 @@ void Component::internalMouseDown (const MouseEvent& event)
12641477
12651478 auto bailOutChecker = BailOutChecker (this );
12661479
1480+ handleKeyboardFocusFromClick ();
1481+
1482+ if (bailOutChecker.shouldBailOut ())
1483+ return ;
1484+
12671485 mouseDown (event);
12681486
12691487 if (bailOutChecker.shouldBailOut ())
0 commit comments