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

Dynamic View and multiple Windows per App #11

Closed
adigitoleo opened this issue Apr 26, 2021 · 7 comments
Closed

Dynamic View and multiple Windows per App #11

adigitoleo opened this issue Apr 26, 2021 · 7 comments

Comments

@adigitoleo
Copy link

Related to #10 but hopefully this issue can serve as a more general discussion about handling Apps with multiple Windows.

The first issue I have encountered is that a View cannot contain exclusively Window components. See the snippet in #10 for which the App is not terminated. I think it's because the App is creating a window for its children, however when these are Windows themselves then the root window is "invisible" and can't be closed. Adding any other component to the View resolves this:

import edifice as ed


class MyApp(ed.Component):
    def render(self):
        return ed.View()(
            ed.Label("Hello"),
            ed.Window()(ed.Label("Hello")),
        )


if __name__ == "__main__":
    ed.App(MyApp()).start()

The second thing is that I am not quite sure how to go about dynamically managing windows. An example of spawning a window from a button click would be interesting (if it is possible). Then there is also the possibility of dynamic Views where some components could be invisible depending on a conditional...

Lastly, the snippet above creates two windows (one for MyApp, implicitly, and one for the second Label). They can be closed independently. In many cases, it would be desirable to assign one window as the root or parent window. When closed, it should close all of it's children and terminate the App.

As time permits, I'll dig into thee implementations some more and hopefully I can help in some way (even if it's just testing or documentation).

@adigitoleo
Copy link
Author

Re: hidden components. I see that this is possible in some way already by using inline condidtionals in the View, e.g.
https://github.com/fding/pyedifice/blob/b93ec3b85ba3c3d71b0431936546ef9fc4e7accd/edifice/components/forms.py#L145

@adigitoleo
Copy link
Author

I see that the FormDialog uses the undocumented List widget, will look into that some more:
https://github.com/fding/pyedifice/blob/b93ec3b85ba3c3d71b0431936546ef9fc4e7accd/edifice/components/forms.py#L282

@fding
Copy link
Member

fding commented May 3, 2021

Here's an example of how to create new windows on the click of a button, and how to close all windows once the main window is closed:

import edifice as ed

class MyApp(ed.Component):
    def __init__(self):
        super().__init__()
        self.spawned_windows = set()
        self.counter = 0
        self.main_window_open = True

    def create_window(self, e):
        with self.render_changes():
            self.spawned_windows = self.spawned_windows | {self.counter}
            self.counter += 1

    def close_window(self, i, e):
        with self.render_changes():
            self.spawned_windows = self.spawned_windows - {i}

    def close_main_window(self, e):
        with self.render_changes():
            self.spawned_windows = set()
            self.main_window_open = False

    def render(self):
        return ed.List()(
            self.main_window_open and ed.Window(on_close=self.close_main_window)(
                ed.Button("New window", on_click=self.create_window),
            ),
            *[
                ed.Window(on_close=lambda e, idx=i: self.close_window(idx, e))(
                    ed.Label(f"Window {i + 1}"),
                ).set_key(str(i))
                for i in self.spawned_windows
            ],
        )

if __name__ == "__main__":
    ed.App(MyApp()).start()

It uses the List component as you noted, which holds a list of subcomponents without mounting them into any UI element. Closing a window is done the same way you would hide an UI element, i.e. by not returning that window in the render function.

I think this solution fits well with the design of the rest of the library. However, the way the library handles windows isn't perfect: when the user closes a window that does not have an on_close event defined, the window will disappear, but the internal state will not register that the window is gone. This means that next time render is called, the window will be resurrected. I'm not sure about the best way of handling this inconsistency. The simplest way is to block the usual window closing mechanism, and only close a window when it's not rendered by anything. But I think this is bad default behavior and would result in overhead for almost all situations. The current version will get all the simple cases right, but could result in inconsistencies with multiple windows if the user isn't careful. Do you have any thoughts on how this might be better designed?

@adigitoleo
Copy link
Author

adigitoleo commented May 4, 2021

Thanks for the clarification. That example is quite helpful: maybe it can replace the current Window example in the docstrings?

Actually, do we even need List if mount_into_window=False in the App call does the same? I missed it before but it seems to work as well, for any (?) top level component in the render function.

I haven't poked around enough yet but could on_close be a positional arg? That would make it explicitly required and perhaps save some confusion. I suppose it would require some sort of stub for on_close when mount_into_window=True.
^ That was silly, I forgot that on_close is optional for the simple cases where there isn't any close_main_window method.

For my use cases it will probably work as-is. I agree that blocking the closing event would be a bit ugly. The only thing I can think of right away that might be convenient is to define a MainWindow component that is basically a shortcut for your above example, with e.g. create_child, close_child methods for managing the child windows... not sure if it's really necessary though.

@adigitoleo
Copy link
Author

when the user closes a window that does not have an on_close event defined, the window will disappear, but the internal state will not register that the window is gone

Is there some way to tell will_unmount to clean that up? I guess it would require hooking into the window closing event rather than blocking... might still be a too troublesome. I'll need to sleep on this.

@adigitoleo
Copy link
Author

Just a heads up that I'm too busy to really look at this, and my original use-case isn't going to happen in the form that I planned. Feel free to close this, or you can leave it open in case there are any takers. I'll definitely recommend that people check out this package if I find anyone looking for simple/reactive UI in python :)

@jamesdbrock
Copy link
Member

Apps with multiple windows is supported in v1.5.0 with the WindowPopView Element.

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

No branches or pull requests

3 participants