Skip to content

Commit 4707752

Browse files
committed
Merge pull request godotengine#63059 from Rindbee/fix-getting-wrong-focus-neighbor-in-ScrollContainer
Fix getting wrong focus neighbor when the control is in ScrollContainer
2 parents 791c87e + e197463 commit 4707752

File tree

2 files changed

+146
-81
lines changed

2 files changed

+146
-81
lines changed

scene/gui/control.cpp

Lines changed: 145 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232

3333
#include "container.h"
3434
#include "core/config/project_settings.h"
35-
#include "core/math/geometry_2d.h"
3635
#include "core/os/os.h"
3736
#include "core/string/translation_server.h"
37+
#include "scene/gui/scroll_container.h"
3838
#include "scene/main/canvas_layer.h"
3939
#include "scene/main/window.h"
4040
#include "scene/theme/theme_db.h"
@@ -2281,18 +2281,9 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
22812281
return c;
22822282
}
22832283

2284-
real_t dist = 1e7;
2284+
real_t square_of_dist = 1e14;
22852285
Control *result = nullptr;
22862286

2287-
Point2 points[4];
2288-
2289-
Transform2D xform = get_global_transform();
2290-
2291-
points[0] = xform.xform(Point2());
2292-
points[1] = xform.xform(Point2(get_size().x, 0));
2293-
points[2] = xform.xform(get_size());
2294-
points[3] = xform.xform(Point2(0, get_size().y));
2295-
22962287
const Vector2 dir[4] = {
22972288
Vector2(-1, 0),
22982289
Vector2(0, -1),
@@ -2302,18 +2293,73 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
23022293

23032294
Vector2 vdir = dir[p_side];
23042295

2305-
real_t maxd = -1e7;
2296+
Rect2 r = get_global_rect();
2297+
real_t begin_d = vdir.dot(r.get_position());
2298+
real_t end_d = vdir.dot(r.get_end());
2299+
real_t maxd = MAX(begin_d, end_d);
23062300

2307-
for (int i = 0; i < 4; i++) {
2308-
real_t d = vdir.dot(points[i]);
2309-
if (d > maxd) {
2310-
maxd = d;
2311-
}
2312-
}
2301+
Rect2 clamp = Rect2(-1e7, -1e7, 2e7, 2e7);
2302+
Rect2 result_rect;
23132303

23142304
Node *base = this;
23152305

23162306
while (base) {
2307+
ScrollContainer *sc = Object::cast_to<ScrollContainer>(base);
2308+
2309+
if (sc) {
2310+
Rect2 sc_r = sc->get_global_rect();
2311+
bool follow_focus = sc->is_following_focus();
2312+
2313+
if (result && !follow_focus && !sc_r.intersects(result_rect)) {
2314+
result = nullptr; // Skip invisible control.
2315+
}
2316+
2317+
if (result == nullptr) {
2318+
real_t sc_begin_d = vdir.dot(sc_r.get_position());
2319+
real_t sc_end_d = vdir.dot(sc_r.get_end());
2320+
real_t sc_maxd = sc_begin_d;
2321+
real_t sc_mind = sc_end_d;
2322+
if (sc_begin_d < sc_end_d) {
2323+
sc_maxd = sc_end_d;
2324+
sc_mind = sc_begin_d;
2325+
}
2326+
2327+
if (!follow_focus && maxd < sc_mind) {
2328+
// Reposition to find visible control.
2329+
maxd = sc_mind;
2330+
r.set_position(r.get_position() + (sc_mind - maxd) * vdir);
2331+
}
2332+
2333+
if (follow_focus || sc_maxd > maxd) {
2334+
_window_find_focus_neighbor(vdir, base, r, clamp, maxd, square_of_dist, &result);
2335+
}
2336+
2337+
if (result == nullptr) {
2338+
// Reposition to search upwards.
2339+
maxd = sc_maxd;
2340+
r.set_position(r.get_position() + (sc_maxd - maxd) * vdir);
2341+
} else {
2342+
result_rect = result->get_global_rect();
2343+
if (follow_focus) {
2344+
real_t r_begin_d = vdir.dot(result_rect.get_position());
2345+
real_t r_end_d = vdir.dot(result_rect.get_end());
2346+
real_t r_maxd = r_begin_d;
2347+
real_t r_mind = r_end_d;
2348+
if (r_begin_d < r_end_d) {
2349+
r_maxd = r_end_d;
2350+
r_mind = r_begin_d;
2351+
}
2352+
2353+
if (r_maxd > sc_maxd) {
2354+
result_rect.set_position(result_rect.get_position() + (sc_maxd - r_maxd) * vdir);
2355+
} else if (r_mind < sc_mind) {
2356+
result_rect.set_position(result_rect.get_position() + (sc_mind - r_mind) * vdir);
2357+
}
2358+
}
2359+
}
2360+
}
2361+
}
2362+
23172363
Control *c = Object::cast_to<Control>(base);
23182364
if (c) {
23192365
if (c->data.RI) {
@@ -2323,11 +2369,15 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
23232369
base = base->get_parent();
23242370
}
23252371

2372+
if (result) {
2373+
return result;
2374+
}
2375+
23262376
if (!base) {
23272377
return nullptr;
23282378
}
23292379

2330-
_window_find_focus_neighbor(vdir, base, points, maxd, dist, &result);
2380+
_window_find_focus_neighbor(vdir, base, r, clamp, maxd, square_of_dist, &result);
23312381

23322382
return result;
23332383
}
@@ -2336,83 +2386,98 @@ Control *Control::find_valid_focus_neighbor(Side p_side) const {
23362386
return const_cast<Control *>(this)->_get_focus_neighbor(p_side);
23372387
}
23382388

2339-
void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest) {
2389+
void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest) {
23402390
if (Object::cast_to<Viewport>(p_at)) {
2341-
return; //bye
2391+
return; // Bye.
23422392
}
23432393

23442394
Control *c = Object::cast_to<Control>(p_at);
2395+
Container *container = Object::cast_to<Container>(p_at);
2396+
bool in_container = container ? container->is_ancestor_of(this) : false;
2397+
2398+
if (c && c != this && c->get_focus_mode() == FOCUS_ALL && !in_container && p_clamp.intersects(c->get_global_rect())) {
2399+
Rect2 r_c = c->get_global_rect();
2400+
r_c = r_c.intersection(p_clamp);
2401+
real_t begin_d = p_dir.dot(r_c.get_position());
2402+
real_t end_d = p_dir.dot(r_c.get_end());
2403+
real_t max = MAX(begin_d, end_d);
2404+
2405+
// Use max to allow navigation to overlapping controls (for ScrollContainer case).
2406+
if (max > (p_min + CMP_EPSILON)) {
2407+
// Calculate the shortest distance. (No shear transform)
2408+
// Flip along axis(es) so that C falls in the first quadrant of c (as origin) for easy calculation.
2409+
// The same transformation would put the direction vector in the positive direction (+x or +y).
2410+
// | -------------
2411+
// | | | |
2412+
// | |-----C-----|
2413+
// ----|---a | | |
2414+
// | | | b------------
2415+
// -|---c---|----------------------->
2416+
// | | |
2417+
// ----|----
2418+
// cC = ca + ab + bC
2419+
// The shortest distance is the vector ab's length or its positive projection length.
2420+
2421+
Vector2 cC_origin = r_c.get_center() - p_rect.get_center();
2422+
Vector2 cC = cC_origin.abs(); // Converted to fall in the first quadrant of c.
2423+
2424+
Vector2 ab = cC - 0.5 * r_c.get_size() - 0.5 * p_rect.get_size();
2425+
2426+
real_t min_d_squared = 0.0;
2427+
if (ab.x > 0.0) {
2428+
min_d_squared += ab.x * ab.x;
2429+
}
2430+
if (ab.y > 0.0) {
2431+
min_d_squared += ab.y * ab.y;
2432+
}
23452433

2346-
if (c && c != this && c->get_focus_mode() == FOCUS_ALL && c->is_visible_in_tree()) {
2347-
Point2 points[4];
2348-
2349-
Transform2D xform = c->get_global_transform();
2350-
2351-
points[0] = xform.xform(Point2());
2352-
points[1] = xform.xform(Point2(c->get_size().x, 0));
2353-
points[2] = xform.xform(c->get_size());
2354-
points[3] = xform.xform(Point2(0, c->get_size().y));
2355-
2356-
// Tie-breaking aims to address situations where a potential focus neighbor's bounding rect
2357-
// is right next to the currently focused control (e.g. in BoxContainer with
2358-
// separation overridden to 0). This needs specific handling so that the correct
2359-
// focus neighbor is selected.
2360-
2361-
// Calculate centers of the potential neighbor, currently focused, and closest controls.
2362-
Point2 center = xform.xform(0.5 * c->get_size());
2363-
// We only have the points, not an actual reference.
2364-
Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]);
2365-
Point2 closest_center;
2366-
bool should_tiebreak = false;
2367-
if (*r_closest != nullptr) {
2368-
should_tiebreak = true;
2369-
Control *closest = *r_closest;
2370-
Transform2D closest_xform = closest->get_global_transform();
2371-
closest_center = closest_xform.xform(0.5 * closest->get_size());
2372-
}
2373-
2374-
real_t min = 1e7;
2375-
2376-
for (int i = 0; i < 4; i++) {
2377-
real_t d = p_dir.dot(points[i]);
2378-
if (d < min) {
2379-
min = d;
2434+
if (min_d_squared < r_closest_dist_squared || *r_closest == nullptr) {
2435+
r_closest_dist_squared = min_d_squared;
2436+
*r_closest = c;
2437+
} else if (min_d_squared == r_closest_dist_squared) {
2438+
// Tie-breaking aims to address situations where a potential focus neighbor's bounding rect
2439+
// is right next to the currently focused control (e.g. in BoxContainer with
2440+
// separation overridden to 0). This needs specific handling so that the correct
2441+
// focus neighbor is selected.
2442+
2443+
Point2 p_center = p_rect.get_center();
2444+
Control *closest = *r_closest;
2445+
Point2 closest_center = closest->get_global_rect().get_center();
2446+
2447+
// Tie-break in favor of the control most aligned with p_dir.
2448+
if (Math::abs(p_dir.cross(cC_origin)) < Math::abs(p_dir.cross(closest_center - p_center))) {
2449+
*r_closest = c;
2450+
}
23802451
}
23812452
}
2453+
}
23822454

2383-
if (min > (p_min - CMP_EPSILON)) {
2384-
for (int i = 0; i < 4; i++) {
2385-
Vector2 la = p_points[i];
2386-
Vector2 lb = p_points[(i + 1) % 4];
2387-
2388-
for (int j = 0; j < 4; j++) {
2389-
Vector2 fa = points[j];
2390-
Vector2 fb = points[(j + 1) % 4];
2391-
2392-
Vector2 pa, pb;
2393-
real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb);
2394-
if (d < r_closest_dist) {
2395-
r_closest_dist = d;
2396-
*r_closest = c;
2397-
} else if (should_tiebreak && d == r_closest_dist) {
2398-
// Tie-break in favor of the control most aligned with p_dir.
2399-
if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) {
2400-
r_closest_dist = d;
2401-
*r_closest = c;
2402-
}
2403-
}
2404-
}
2455+
ScrollContainer *sc = Object::cast_to<ScrollContainer>(c);
2456+
Rect2 intersection = p_clamp;
2457+
if (sc) {
2458+
if (!sc->is_following_focus() || !in_container) {
2459+
intersection = p_clamp.intersection(sc->get_global_rect());
2460+
if (!intersection.has_area()) {
2461+
return;
24052462
}
24062463
}
24072464
}
24082465

24092466
for (int i = 0; i < p_at->get_child_count(); i++) {
24102467
Node *child = p_at->get_child(i);
24112468
Control *childc = Object::cast_to<Control>(child);
2412-
if (childc && childc->data.RI) {
2413-
continue; //subwindow, ignore
2469+
if (childc) {
2470+
if (childc->data.RI) {
2471+
continue; // Subwindow, ignore.
2472+
}
2473+
if (!childc->is_visible_in_tree()) {
2474+
continue; // Skip invisible node trees.
2475+
}
2476+
if (Object::cast_to<ScrollContainer>(childc) && childc->is_ancestor_of(this)) {
2477+
continue; // Already searched in it, skip it.
2478+
}
24142479
}
2415-
_window_find_focus_neighbor(p_dir, p_at->get_child(i), p_points, p_min, r_closest_dist, r_closest);
2480+
_window_find_focus_neighbor(p_dir, child, p_rect, intersection, p_min, r_closest_dist_squared, r_closest);
24162481
}
24172482
}
24182483

scene/gui/control.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ class Control : public CanvasItem {
314314

315315
// Focus.
316316

317-
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest);
317+
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest);
318318
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
319319

320320
// Theming.

0 commit comments

Comments
 (0)