Skip to content
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

Update TabbedContent with methods to add, remove and clear tabs/panes #2751

Merged
merged 34 commits into from
Jun 16, 2023

Conversation

davep
Copy link
Contributor

@davep davep commented Jun 7, 2023

This PR extends TabbedContent with some new methods, and also adds a new message. The new methods are:

  • add_pane to add a new TabPane to the widget
  • remove_pane to remove an existing TabPane from the widget
  • clear_panes to remove all existing panes from the widget
  • tab_count (okay, actually a property) to give easy access to the underlying Tabs.tab_count

The new message is TabbedContent.Cleared, which, like with Tabs, is posted when the number of tabs in the widget falls to zero.

In the process of working on this, a small change has also been made to Underline and Tabs to counter the case where an animation of the underline is still taking place when the target tab has been removed.


Work on this was the cause for #2750 being noted.

davep added 16 commits June 6, 2023 12:02
If, on the other hand, we set active to empty when there is no content to be
tabbed, then we let it slide.
Mimics (and actually simply returns) Tabs.tab_count. The idea being that if
people can now add and remove tabs from TabbedContent, they may want to be
able to keep track of how many tabs there are.
Much like Tabs.Cleared, this indicates that all available tabs/panes have
been removed and the widget is now empty. This is especially important here
as the way we remove tabs is such that we can't await their removal and then
make the remove methods async (because Tabs doesn't allow for that).

So the approach taken here is to send a message from TabbedContent, and
delay it as much as possible, ideally once the action that's taking
place *has* taken place.

The reasoning is: a user may clear down all panes, then want to add some
back, possibly with IDs they've used before. The clear down might not have
fully happened, but we can't await it all, so the approach for the user
would be to wait until the Cleared message turns up *then* repopulate.
The tests touched in this commit are working fine in CI for GNU/Linux and
macOS; but fail on Windows as the message we need to come through doesn't
seem to be coming through.

Testing on Windows (11, in Parallels, on macOS) it seems that setting an
actual time for the pauses does the trick. I'm not sure why, I thought a
pause with no time ensured that all message queues were emptied before
coming out of the pause. Apparently not.

So this is an experiment to see if it'll pass in CI too.
@davep davep added enhancement New feature or request Task labels Jun 7, 2023
@davep davep linked an issue Jun 7, 2023 that may be closed by this pull request
@davep davep changed the title Updated TabbedContent with methods to add, remove and clear tabs/panes Update TabbedContent with methods to add, remove and clear tabs/panes Jun 7, 2023
It's possible that we might be being asked to switch from an item of content
that has actually been removed; there's no harm in making not finding the
old thing a no-op.
It's possible, unlikely but possible, that the content could get removed via
some other route, or out of sync, so allow for that. Don't get upset of the
content has gone away when we're removing the tab that was in charge of it.
Ideally this would use something like what Textualize#2786 intends to add, but
meanwhile this solves the problem of ghost highlights in extreme situations
of adding/removing tabs.
@davep davep marked this pull request as ready for review June 15, 2023 10:43
Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

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

Not a full review, just a few comments

src/textual/widgets/_tabbed_content.py Outdated Show resolved Hide resolved
"""
if not active:
raise ValueError("'active' tab must not be empty string.")
if not active and self.get_child_by_type(ContentSwitcher).current:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there any chance the dev might attempt to set active earlier, and there won't be a ContentSwitcer yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the only way that could arise is if they do that before the widget is composed, but even without this change that would end up with a similar issue due to the triggering of _watch_active, right? Attempting to set the active tab to a pane that doesn't even exist yet is never going to go well.

src/textual/widgets/_tabbed_content.py Outdated Show resolved Hide resolved
src/textual/widgets/_tabbed_content.py Outdated Show resolved Hide resolved
Comment on lines 88 to 93
def __await__(self) -> Generator[None, None, None]:
async def await_tabbed_content() -> None:
for awaitable in self._awaitables:
await awaitable

return await_tabbed_content().__await__()
Copy link
Contributor

Choose a reason for hiding this comment

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

I need to learn more about __await__.
Is it a "standard" approach to implement __await__ with a wrapper around what we care about and then call __await__ on the wrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Calling @willmcgugan -- I'm mostly just following the pattern laid down by AwaitMount and AwaitRemove; doubtless Will will have the detail.

src/textual/widgets/_tabbed_content.py Outdated Show resolved Hide resolved
tests/test_tabbed_content.py Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you say it's worth adding a test for add_pane that uses after, another with before, and another that checks we get an error when providing both after and before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, good point, TabbedContent could do with the same tests the underlying Tabs has; forgot to add those when I added before and after support up here.

davep and others added 4 commits June 15, 2023 12:04
Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
So they don't get confused with actual "add after".
@davep
Copy link
Contributor Author

davep commented Jun 15, 2023

This PR can also close #2427 which does something similar, although as a subset of what this PR does.

@davep davep merged commit c966243 into Textualize:main Jun 16, 2023
@davep davep deleted the tabbed-content-redux branch June 16, 2023 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Task
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add the ability to add tabs to TabbedContent
3 participants