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
0 commit comments