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

Parent visibility not respected on add #3161

Open
HalfWhitt opened this issue Feb 5, 2025 · 5 comments
Open

Parent visibility not respected on add #3161

HalfWhitt opened this issue Feb 5, 2025 · 5 comments
Labels
bug A crash or error in behavior.

Comments

@HalfWhitt
Copy link
Contributor

HalfWhitt commented Feb 5, 2025

Describe the bug

#2950 fixed inherited visibility in descendant widgets... but not entirely, it turns out. That test verifies that everything behaves as expected when the visibility property is set after the widget hierarchy is already established. However, the mechanism that looks up the chain for a hidden ancestor isn't done when adding a child.

The simplest fix for this is to put child.style.apply("visibility", child.style.visibility) in Widget.add, but that seems like mixing concerns — Widget probably shouldn't be special-casing a specific Pack property like that, and it can't be tested in the current test_apply.py setup which uses dummy example nodes instead of actual widgets.

Perhaps Pack — or even BaseStyle? – could gain a hook to be called once its node has been added to a new parent. So child.style.apply("visibility", child.style.visibility) would become something like child.style._added_to_new_parent(), so the style engine can perform whatever inheritance-related checks it needs to do. This could potentially go in Node.add. Thoughts?

Steps to reproduce

def test_set_visibility_inherited_on_add():
    """Nodes should be hidden when added to an already-hidden parent."""
    parent = ExampleParentNode("parent", style=Pack(visibility=HIDDEN))
    child = ExampleNode("child", style=Pack())
    parent.add(child)
    child._impl.set_hidden.assert_called_once_with(True)

Expected behavior

A child's on-screen visibility should respect an ancestor's hidden status upon being added as its child.

Screenshots

No response

Environment

  • Toga: Main branch

Logs

Additional context

No response

@HalfWhitt HalfWhitt added the bug A crash or error in behavior. label Feb 5, 2025
@freakboy3742
Copy link
Member

Nice catch!

You mention special casing visibility when a node is added to a parent... but is there any reason this shouldn't just be a reapply() of the entire style? Or the point at which the applicator is actually added to the widget?

Although you've identified visibility as a property that is affected by this - but that's because visibility is the only property in Pack that can be affected by the parent style value. In future, when we have full CSS... cascading means that parents can impact on any style.

At the very least, I'd argue that this isn't a "visibility" feature - it's a "refresh cascading properties" feature. I guess that's what is implied by your _add_to_new_parent() API; but it seems to me that this is something we can track internally because we know when a widget has a change in parent (or we can know, because the widget parent is stored at the Travertino level), rather than needing a new API that is invoked at the Toga level.

@freakboy3742
Copy link
Member

In the process of reviewing #2484, I've noticed a case where cascading is already an issue - background color on Winforms requires reapplying background color when the parent is changed because of how transparency is handled. So a style-wide approach would seem to make sense here.

@HalfWhitt
Copy link
Contributor Author

HalfWhitt commented Feb 6, 2025

You mention special casing visibility when a node is added to a parent... but is there any reason this shouldn't just be a reapply() of the entire style?

That's true, it could just be a reapply(). It would apply all properties, including non-cascading ones, but it would be simple.

However, another wrinkle I didn't consider is that this needs to propagate downward. When you add Parent to Grandparent, Child also needs to compute its cascaded properties. The way visibility is currently implemented, it searches upwards, potentially all the way to the root — but this will snowball when propagated downward.

We'd only need a single tree traversal if, instead, added_to_new_parent (cascade is probably a better name) carries the computed values downward with it as an argument. (I suppose we could accomplish the same thing by "caching" any computed cascading properties on each widget, and making its child's reapply method check there, but that seems messier.)

Or the point at which the applicator is actually added to the widget?

Hm... currently we assign an applicator when the widget is first created. Are you suggesting we avoid assigning an applicator then, and only assign it once the node is given a parent? There's nothing stopping you from adding a widget to a parent that doesn't currently have a parent or applicator of its own, and reparenting a widget that has children still needs to propagate the changes downward.

At the very least, I'd argue that this isn't a "visibility" feature - it's a "refresh cascading properties" feature. I guess that's what is implied by your _add_to_new_parent() API; but it seems to me that this is something we can track internally because we know when a widget has a change in parent (or we can know, because the widget parent is stored at the Travertino level), rather than needing a new API that is invoked at the Toga level.

I think we're on the same page here. Node.add(), in Travertino, would include a line notifying the style that its node's parent has changed, presumably by calling a cascade method on it.

In the process of reviewing #2484, I've noticed a case where cascading is already an issue - background color on Winforms requires reapplying background color when the parent is changed because of how transparency is handled. So a style-wide approach would seem to make sense here.

Interesting, I'll have to take a look at that one.

@freakboy3742
Copy link
Member

However, another wrinkle I didn't consider is that this needs to propagate downward. When you add Parent to Grandparent, Child also needs to compute its cascaded properties. The way visibility is currently implemented, it searches upwards, potentially all the way to the root — but this will snowball when propagated downward.

We definitely need to make sure we're not setting off a notification cascade by working in both directions. However, I don't think that's a concern here.

Application in both directions is already happening. The apply method on Pack looks to the parent to establish if the parent is hidden; but when you use the applicator, the applicator iterates over children to apply the new effective hidden state. The difference is between the style value and the effective value.

We'd only need a single tree traversal if, instead, added_to_new_parent (cascade is probably a better name) carries the computed values downward with it as an argument. (I suppose we could accomplish the same thing by "caching" any computed cascading properties on each widget, and making its child's reapply method check there, but that seems messier.)

In the case you're describing, adding parent to grandparent means you need to interrogate Grandparent's visibility (which means potentially looking all the way to root); but the re-application of style is applied from Parent down. The style attribute of each node doesn't change; but the effective value passed to the applicator might. But this is already implemented; the only difference here is triggering the apply() on parent when it is re-parented.

Or the point at which the applicator is actually added to the widget?

Hm... currently we assign an applicator when the widget is first created. Are you suggesting we avoid assigning an applicator then, and only assign it once the node is given a parent? There's nothing stopping you from adding a widget to a parent that doesn't currently have a parent or applicator of its own, and reparenting a widget that has children still needs to propagate the changes downward.

Broadly speaking, that's what I was suggesting - but to be clear, it's also me noodling around looking for other ways to approach the problem, not a coherent API proposal. I'm mostly observing that when an applicator is assigned, one of the side effects is to apply the entire style... which is essentially what we're looking to do here. If we're able to combine the two requirements, that seems like a win.

At the very least, I'd argue that this isn't a "visibility" feature - it's a "refresh cascading properties" feature. I guess that's what is implied by your _add_to_new_parent() API; but it seems to me that this is something we can track internally because we know when a widget has a change in parent (or we can know, because the widget parent is stored at the Travertino level), rather than needing a new API that is invoked at the Toga level.

I think we're on the same page here. Node.add(), in Travertino, would include a line notifying the style that its node's parent has changed, presumably by calling a cascade method on it.

Bikeshed color and bugs we haven't thought of notwithstanding, 👍

@HalfWhitt
Copy link
Contributor Author

However, another wrinkle I didn't consider is that this needs to propagate downward. When you add Parent to Grandparent, Child also needs to compute its cascaded properties. The way visibility is currently implemented, it searches upwards, potentially all the way to the root — but this will snowball when propagated downward.

We definitely need to make sure we're not setting off a notification cascade by working in both directions. However, I don't think that's a concern here.

Application in both directions is already happening. The apply method on Pack looks to the parent to establish if the parent is hidden; but when you use the applicator, the applicator iterates over children to apply the new effective hidden state. The difference is between the style value and the effective value.

But this is already implemented; the only difference here is triggering the apply() on parent when it is re-parented.

Wow, I've been staring at style so hard I forgot that the applicator already does this. Yeah, okay, this (probably) isn't as big a change as I was thinking for a second there.

Bikeshed color and bugs we haven't thought of notwithstanding, 👍

It's getting late for me here, but soon (probably tomorrow) I can see about putting together at least a proof of concept... and then we can kibitz about what color to paint it. 😉

I'm mostly observing that when an applicator is assigned, one of the side effects is to apply the entire style... which is essentially what we're looking to do here. If we're able to combine the two requirements, that seems like a win.

Ah, yeah I think I see where you're going there. I'm not sure either what that might look like, yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A crash or error in behavior.
Projects
None yet
Development

No branches or pull requests

2 participants