Skip to content

Implement automatic snapping in split containers #106323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

badsectoracula
Copy link
Contributor

@badsectoracula badsectoracula commented May 12, 2025

Closes godotengine/godot-proposals#12417.

Implement automatic snapping in split containers so that when the user drags the splitter beyond a threshold after the minimum size of a child, it "snaps" to the appropriate edge (left/right for horizontal arrangement, top/bottom for vertical arrangement), causing the child look as if it disappeared. The splitter can still be dragged back to reveal the child.

Visually this looks similar to collapsing but functionally it differs as it only affects the visual state of the nodes.

The proposal contains a video of this in action.

@badsectoracula badsectoracula requested review from a team as code owners May 12, 2025 21:47
@badsectoracula badsectoracula force-pushed the pr-splitcontainer-autosnap branch from df9270c to 42271a0 Compare May 12, 2025 21:56
@Calinou Calinou added this to the 4.x milestone May 12, 2025
Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it mostly works as expected.

Some feedback:

  • There's an issue where clicking the bottom panel for the first time will hide it immediately (since it tries to resize to the minimum possible size, which now hides it entirely). This is also why the bottom panel isn't visible anymore while the editor is still starting (and restoring scenes from the previous session).

  • Hiding the bottom panel completely when you drag it all the way down might be confusing. I can see a lot of users wondering where the bottom panel went and having to resort to restarting the editor to get it back.

    • Maybe dragging the bottom panel all the way down should just collapse it (like when you click on the name of the current bottom panel), and hiding it entirely should be left to a dedicated button in the bottom panel.

    • This issue also applies to the side docks, but it's probably less extreme. In any case, we might need to show some kind of notice the first time you collapse any of the docks in an editor session. One of my UX pet peeves in software is when I manage to hide something and have no idea how to undo it 🙂1

  • Additionally, if you hide both docks and toggle distraction-free mode, it will appear to do nothing (except cause a very slight UI shift on the left side, which may indicate a leftover spacer control or similar):
distraction_free_hide_docks.mp4
  • This is a good opportunity to make distraction-free mode hide the bottom panel again, now that you get more control over what to hide. This has been a recurring request over the years: godotengine/godot-proposals#9141

Footnotes

  1. If you've pressed Tab while using GIMP, you know.

@syntaxerror247
Copy link
Member

Sorry about the misclick — I'm on mobile.

@syntaxerror247
Copy link
Member

While testing this PR, I noticed that in the ProjectSettings or EditorSettings window, the left panel was already snaps to the edge. This behavior doesn't seem intentional — is that expected?

@arkology
Copy link
Contributor

arkology commented May 13, 2025

Also it's possible to hide center panel, this is definitely not intended 😄

@badsectoracula badsectoracula force-pushed the pr-splitcontainer-autosnap branch from 42271a0 to 289e7af Compare May 13, 2025 23:39
@badsectoracula badsectoracula requested review from a team as code owners May 13, 2025 23:39
@badsectoracula badsectoracula force-pushed the pr-splitcontainer-autosnap branch from 289e7af to 61af908 Compare May 13, 2025 23:40
…r drags the splitter beyond a threshold after the minimum size of a child, it "snaps" to the appropriate edge (left/right for horizontal arrangement, top/bottom for vertical arrangement), causing the child look as if it disappeared. The splitter can still be dragged back to reveal the child.

Visually this looks similar to collapsing but functionally it differs as it only affects the visual state of the nodes.
@badsectoracula badsectoracula force-pushed the pr-splitcontainer-autosnap branch from 61af908 to 780a344 Compare May 14, 2025 00:14
@badsectoracula
Copy link
Contributor Author

Made some changes based on the comments:

  • Instead of trying to infer snapping from the splitter position (split offset) the code now uses an explicit snap state. The snap state can be set programmatically (this also allows saving the snap state as part of the editor layout).
  • Autosnapping updates the snap state based on the flags like before (i changed the constant names to reflect that they're used for both autosnap setting and the snap state).
  • A signal is emitted whenever the snap state changes.
  • The dragging state of the dragger is reset when a property (e.g. collapsed) is set to a value that causes dragging impossible while dragging (this was an existing bug that i found while trying to implement the other stuff).
  • For the bottom button panel when dragging ends that would end up in a snapped position, the snapping is reset and instead:
    • When trying to snap at the bottom of the screen, the panel collapses its (as if someone clicked on the active button)
    • When trying to snap at the top of the screen, the editor enters in distraction free mode
  • The snapping state for the dock splits is now stored in the current editor layout. The default layout is updated with snapping disabled (so someone selecting to restore the default layout with snapped dock splitters will have these splitters unsnapped).

For the bottom button panel i tried to have it enter in the collapsed mode automatically while dragging but because the buttons themselves are inside the splitter and collapsing it causes all sorts of other side effects the code was becoming way too gnarly for something that seems like a very special case, so instead i made the bottom panel collapse after the drag finishes (the effect is almost the same, except you can drag the splitter all the way to the bottom but once you release the mouse button the buttons "pop" back out). To make this happen while dragging it'd require moving the buttons outside the splitter, but this felt like a much more intrusive change to me.

Also note that while the default setting is to allow autosnapping for both children, this can be changed in cases where autosnapping needs to be done only for one child or disabled. However i think having the default be to autosnap for both is more useful as it'd allow pretty much every splitter to snap and save screen real estate across the editor without any changes (it does cause the unit tests for dragging to fail though so i'll need to see what to do with that).

@KoBeWi
Copy link
Member

KoBeWi commented May 26, 2025

Using FIRST/SECOND as snap target will conflict with #90411
Not sure how to handle that though, ideally the other PR should be merged first, but it may take a while 🙃

@AThousandShips AThousandShips changed the title Implement automatic snapping in split containers so that when the use… Implement automatic snapping in split containers May 26, 2025
@AThousandShips
Copy link
Member

Btw the title of this commit is far too long, the commit description and title should be brief and clear

case NOTIFICATION_EXIT_TREE: {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);
center_split->disconnect(SNAME("drag_ended"), callable_mp(this, &EditorBottomPanel::_center_split_drag_ended));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you disconnecting the signal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restoring the state as it was before the node entered the tree since i connect the signal on NOTIFICATION_ENTER_TREE i thought it'd be a good idea to disconnect it in NOTIFICATION_EXIT_TREE.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But bottom panel enters and exits tree when the editor starts and closes, it happens only once. It's irrelevant if the signal stays connected.

Copy link
Contributor Author

@badsectoracula badsectoracula Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, if it happens only once then it is not necessary to remove it, i'll update the code later to remove this part.

@@ -100,6 +112,31 @@ void EditorBottomPanel::_update_disabled_buttons() {
right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
}

void EditorBottomPanel::_center_split_drag_started() {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this check is only needed once, when connecting the signal. The bottom dock does not move.

Comment on lines +204 to +205
void SplitContainer::_fit_child_in_rect_with_visibility_update(Control *p_child, const Rect2 &p_rect) {
// For very small rects (like when a size is set to 0 via autosnapping) hide the child instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is misleading, because visibility is not being changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on how you think about it, from a technical perspective it changes the scale so it doesn't actually change its visibility flag, but from the user's perspective it makes the child disappear, so it does change its visibility. The goal of the function is to update the visibility from the end user's perspective even if from the technical side it changes its scale - hence the reason i added the comment to clarify what is going on and why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could say like "disappear from view" because "hide", "show", "visibility", etc. have very specific meanings for CanvasItem & co.

@@ -245,7 +263,26 @@ void SplitContainer::_compute_split_offset(bool p_clamp) {
// Clamp the split offset to acceptable values.
int first_min_size = first->get_combined_minimum_size()[axis_index];
int second_min_size = second->get_combined_minimum_size()[axis_index];
computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size);

// Check autosnapping
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check autosnapping
// Check autosnapping.

Same in all comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make SplitContainer hide child controls when the user drags the split beyond the minimum size
7 participants