Skip to content

Commit 8a8b899

Browse files
committed
Fix keyboard focus handling
1 parent c1dbef9 commit 8a8b899

14 files changed

Lines changed: 1897 additions & 25 deletions
412 KB
Loading

modules/yup_gui/component/yup_Component.cpp

Lines changed: 219 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Component::Component (StringRef componentID)
3737

3838
Component::~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+
905924
void 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+
940973
NamedValueSet& 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

11281296
void 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())

modules/yup_gui/component/yup_Component.h

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,32 @@ class YUP_API Component : public MouseListener
746746
*/
747747
void setWantsKeyboardFocus (bool wantsFocus);
748748

749+
/**
750+
Check whether this component wants keyboard focus.
751+
752+
@return True if this component wants keyboard focus, false otherwise.
753+
*/
754+
bool getWantsKeyboardFocus() const;
755+
756+
/**
757+
Set whether clicking this component can make it grab keyboard focus.
758+
759+
When a component is clicked, focus handling walks from the clicked
760+
component up through its parents until it finds a component that both
761+
wants keyboard focus and has this flag enabled. This is enabled by
762+
default.
763+
764+
@param shouldGrabFocus True if mouse clicks can grab keyboard focus.
765+
*/
766+
void setClickingGrabFocus (bool shouldGrabFocus);
767+
768+
/**
769+
Check whether clicking this component can make it grab keyboard focus.
770+
771+
@return True if mouse clicks can grab keyboard focus.
772+
*/
773+
bool getClickingGrabFocus() const;
774+
749775
/**
750776
Take the focus.
751777
*/
@@ -1152,6 +1178,45 @@ class YUP_API Component : public MouseListener
11521178
*/
11531179
std::optional<var> findStyleProperty (const Identifier& propertyId) const;
11541180

1181+
#if YUP_ENABLE_COMPONENT_PAINT_PROFILING
1182+
//==============================================================================
1183+
/** Enables or disables paint profiling for this component.
1184+
1185+
When enabling, a PaintProfileStats instance is created (if not already present)
1186+
and the component is registered with PaintProfiler. Existing samples are
1187+
preserved when re-enabling with the same capacity.
1188+
1189+
When disabling, the component is deregistered from PaintProfiler and the
1190+
stats object is destroyed.
1191+
1192+
@param shouldBeEnabled Pass true to enable, false to disable.
1193+
@param options Controls the ring buffer capacity and recording behaviour.
1194+
*/
1195+
void setPaintProfilingEnabled (bool shouldBeEnabled,
1196+
PaintProfileOptions options = {});
1197+
1198+
/** Returns true if paint profiling is currently enabled for this component. */
1199+
bool isPaintProfilingEnabled() const;
1200+
1201+
/** Clears all paint profile samples recorded for this component. */
1202+
void resetPaintProfiling();
1203+
1204+
/** Returns a pointer to this component's PaintProfileStats, or nullptr if
1205+
profiling is not enabled. */
1206+
PaintProfileStats* getPaintProfileStats();
1207+
1208+
/** Returns a const pointer to this component's PaintProfileStats, or nullptr
1209+
if profiling is not enabled. */
1210+
const PaintProfileStats* getPaintProfileStats() const;
1211+
1212+
/** Returns the display name used for this component in profiling snapshots.
1213+
1214+
Prefers (in order): a non-empty component title, a non-empty component ID,
1215+
the demangled type name if available, then falls back to "Component".
1216+
*/
1217+
String getPaintProfileName() const;
1218+
#endif
1219+
11551220
//==============================================================================
11561221
/** A bail out checker for the component. */
11571222
class BailOutChecker
@@ -1233,6 +1298,8 @@ class YUP_API Component : public MouseListener
12331298
void internalAttachedToNative();
12341299
void internalDetachedFromNative();
12351300

1301+
void handleKeyboardFocusFromClick();
1302+
12361303
void updateMouseCursor();
12371304

12381305
bool hasOpaqueChildCoveringArea (const Rectangle<float>& area);
@@ -1266,6 +1333,7 @@ class YUP_API Component : public MouseListener
12661333
bool isTransparent : 1;
12671334
bool unclippedRendering : 1;
12681335
bool wantsKeyboardFocus : 1;
1336+
bool clickingDoesNotGrabFocus : 1;
12691337
bool isRepainting : 1;
12701338
bool blockSelfMouseEvents : 1;
12711339
bool blockChildrenMouseEvents : 1;
@@ -1277,11 +1345,15 @@ class YUP_API Component : public MouseListener
12771345
Options options;
12781346
};
12791347

1280-
#if YUP_ENABLE_COMPONENT_REPAINT_DEBUGGING
1348+
#if YUP_ENABLE_COMPONENT_PAINT_DEBUGGING
12811349
Color debugColor = Color::opaqueRandom();
12821350
int counter = 2;
12831351
#endif
12841352

1353+
#if YUP_ENABLE_COMPONENT_PAINT_PROFILING
1354+
std::unique_ptr<PaintProfileStats> paintProfileStats;
1355+
#endif
1356+
12851357
YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Component)
12861358
};
12871359

0 commit comments

Comments
 (0)