Pysimplegui: [Request] Add PySimpleGUI Remi for WebApps (PySimpleGUIRemi port)

Created on 20 Jan 2019  路  20Comments  路  Source: PySimpleGUI/PySimpleGUI

Hi,
first of all I love your project. To make it somehow complete in terms of having a Web Gui I propose to make a port for the 100 kb 100% Python Web Gui library Remi.

https://github.com/dddomodossola/remi

Remi is easy to use out of the box but I like your way of Gui definition/design more. Remi is blazingly fast due to usage of websockets. Most of the standard widgets are available.

Just a proposal. Close the issue if you don't like the idea.

Regards
Chris

In progress enhancement

Most helpful comment

I indeed have been in touch with Davide! He's AWESOME to work with. He wrote a bunch of code that has helped the port along. All I have to do is ask and he's all over the request.

I know nothing about CSS and JS. I'll eventually get to things like themes or exposing more of the underlying remi capabilities. Right now I'm focused on getting the core PySimpleGUI elements up and running. I need to get the basics down before diving too much into the fancy stuff.

While it seems to be moving quickly, it's also slow going. Davide is amazing at providing help. It makes up, to some extent, for the lack of documentation. It slows the development however to not be able to quickly search for answers like I did on the other ports.

The "problem" with PySimpleGUI is that while other people writing GUI applications need to research and learn only the parts of the framework that apply directly to their project. PySimpleGUI ports, on the other hand, require the use of almost ALL of the framework's features.

Remi has been great to work with. "It just works" applies as I've not hit any bugs that block progress.

Agreed that this is one of the fastest "feature request implementations ever". The other part of the statement is that it's one of the "largest feature requests ever".

One of the most difficult parts is going to be the damned Popups. I cannot easily add secondary windows. I will need to use the "Dialog Box" mechanism of remi which is an entirely different way to create a "window". VERY different types of calls to get elements shown on the screen. It's like creating a completely different port to get these additional windows.

All 20 comments

I LIKE it!

I was looking at Remi a couple of weeks back. I keep returning to it, marveling at how nice the widgets look. The challenge will is transforming the Remi API into the PySimpleGUI API and architecture.

It would be amazing to be able to run a PySimpleGUI program in a browser.

If you have suggestions on how to approach the port I would like to hear more.

It appears that a Remi port is possible. I'll have to do something I've not done with the other ports, run the GUI in a different thread than the user application.

This is required because I don't see a way of "exiting" the "mainloop" in Remi. The call to start() doesn't return until a window is closed. A call to Window.Read() in PySimpleGUI needs to return after a timeout has occurred or immediately if the timeout parameter is zero.

I'm excited about this port!! This is the most viable way of getting PySimpleGUI up and running in a web browser than I've seen before. I hope to have something up and running this week.

Just released version 0.1.1 to PyPI of PySimpleGUIWeb

This is such a cool port! I didn't think it was possible to integrate with Remi like this. It's turned out the be the perfect way of interfacing through a browser.

Thank you for suggesting it! The best ideas for PySimpleGUI have come from users. I simply implement other people's ideas and claim them for myself.

Oh, even timeouts are operational! Never dreamed this much would be working in the first day of the port.

Released version 0.4.0 today. This port is moving along!

I'm going to close this request since the project is officially started.

Hi Mike,
I just have to say: WOW. This was the fastest Feature Request with implementation ever. :-)

Did you talk to Davide from Remi? He is really a nice person and can give you amazing support via gitter very quickly if you should run into any issues.

Maybe it's worth to notice, that you can also add custom CSS and JS to remi. I made an example using the Bootstrap library to make remi look like a typical Bootstrap App (Its inside examples from contributors in the remi repo). You then just have to add the Bootstrap classes to the widgets. Also it works great with the DASH css files that look also great.

Maybe we could define class names for certain controls in order to make contributeted CSS Themes possible. You just have to load the CSS and set the right class name and at once the GUI has got another look and feel. I am not really good at CSS but many other are.

But again, this is simply awesome.

Thanks for that.

regards
Chris

I indeed have been in touch with Davide! He's AWESOME to work with. He wrote a bunch of code that has helped the port along. All I have to do is ask and he's all over the request.

I know nothing about CSS and JS. I'll eventually get to things like themes or exposing more of the underlying remi capabilities. Right now I'm focused on getting the core PySimpleGUI elements up and running. I need to get the basics down before diving too much into the fancy stuff.

While it seems to be moving quickly, it's also slow going. Davide is amazing at providing help. It makes up, to some extent, for the lack of documentation. It slows the development however to not be able to quickly search for answers like I did on the other ports.

The "problem" with PySimpleGUI is that while other people writing GUI applications need to research and learn only the parts of the framework that apply directly to their project. PySimpleGUI ports, on the other hand, require the use of almost ALL of the framework's features.

Remi has been great to work with. "It just works" applies as I've not hit any bugs that block progress.

Agreed that this is one of the fastest "feature request implementations ever". The other part of the statement is that it's one of the "largest feature requests ever".

One of the most difficult parts is going to be the damned Popups. I cannot easily add secondary windows. I will need to use the "Dialog Box" mechanism of remi which is an entirely different way to create a "window". VERY different types of calls to get elements shown on the screen. It's like creating a completely different port to get these additional windows.

Maybe you could use the generic Widget (= div tag) with absolute positioning and just add it to the viewport. As it takes CSS it should be possible to use this as a container for pop-up. It's not really a pop-up but looks like one. You just have to disable all other Widget while it's in front in order to simulate a modal behaviour. I don't know how it's organized in PSG but you could most likely iterate over normal widgets to disable them with attribute feature and add the Dialog. I would leave the Remi Dialogs alone in this case.

I need a solution for "multiwindow" applications. The only way I see that happening is to use the dialog interface.

It would be ideal if I could hide the entire "window" and make a new one. I didn't see a way to do it other than dialogs.

These windows need to be just like any other window a user can create.

You can create a lot of Widgets which can be containers as you probably already know.
You can pack child widgets in these containers even without showing them at all-just an instance of a container widget in memory. You can even update hidden widget containers and their child without problems. They stay alive ;)

Then you can change the root widget. There is a setter function for that. Also you can exchange child widgets by removing the old one and inserting another one. There is a multiscreen example in contributers example folder.

Thanks! I use VBox and HBox all over the place. I also support invisible elements so I've got code to hide things.

I think that it could be a trivial change, but then again it may be impossible.

Maybe this will work..

  • When a window is created, it is made inside of a "master sizer" widget (VBox in this case)
  • A new window is started, it will hide the "master sizer" for the prior window
  • A new "master sizer" is created (a VBox)
  • remi is told to hide the currently running VBox
  • remi is told to show the new VBox

This is how I see it fitting into the PySimpleGUI architecture.

I will need to write some code that deals with the first window and different code that handles the future windows (nested windows). The remi portion of the code is currently running as a thread. I would continue to run it as a single thread. It's going to be a bit of a pain in the ass to do some of this, but will be worth it if I can not have to use the Dialog interfaces.

Questions

Can I call remi and switch the window to the new VBox while the Start command is still running?

Call from where? You can add a listener connect to an event which can do the switching. You could also implement a loop which is checking some structure outside remi e. G. a Dict or List. There is already such a loop, the idle method of the app. It's constantly called if nothing happens on the Gui. I use it to update widgets like on a dashboard.

The app is showing something when main() returns the widget. I don't know if something is happening beforehand. But you can instantiate every widget (also child's in widget) with self instead the way in the examples and then add them to their parent widget, so you have access to all widgets (parent or child) from everywhere directly via their name (eg self.MyButton1).

If you want exchange data via an external Dict or similar look for the userdata app parameter. It takes a tuple with external variables, objects or whatever. Like that you can connect remi to the outside world of projects.
HTH

I made an example for switching between different screens.

https://pastebin.com/HDFLB2xr

I added code last night that successfully hid and restored a window layout. It was only a couple of lines of code that I added to my main (user's) 'application code. This code will be migrated down into the PySimpleGUI code itself of course.

while True:             # Event Loop
    event, values = window.Read()
    print(event, values)
    if event is None or event == 'Exit':
        break
    if event == 'Show':
        window.master_widget.attributes['hidden'] = 'true'
        time.sleep(2)
        del(window.master_widget.attributes['hidden'])

master_widget is my VBox widget that holds the entire window layout. Side comment - I have questions about how to center and do other operations on a VBox. I have been trying to offer a "justification" setting for Column Elements so that you can choose centered and all of the Elements in the Column will be centered in the column.

Now I just need to add the ability to show another window.

Thanks for the Idle callback tip. This sounds like where I need to add the code that will switch over to the "new" window after I hide the current window. I was planning on implementing a LIFO list of windows. When a window is exited, the code will "unhide" the prior window and tell remi that it's active again.

I made another example with differnt windows which live in a Dict. While creating the Screens I pass one method of the main app (uiControl) to the screens as a callback. If I call that one with an additional screen name, I can directly remove the old view with self.content.remove_child(self.content.children['view']) and add another screen from the Dict like self.content.add_child('view', self.MyScreens['screenname'].

remi_multiscreen.zip

So you have a single method uiControl('screenname') to switch to other screens/windows. Very easy.
You can bind that to the click event of all UI elements, so in principle you can have unlimited windows or dialogs.

You could also use a main eventhandler in the main app and steer it with strings and decide in this one and only handler what to do. This could be an approach too, because you wouldn't need any handlers in the window classes.

I also use the idle method to update only the actual viewport. You can implement an update method inside a screen and call it from idle with like self.content.children['view].updateScreen()

By doing this each view/window can take care of its own update, which is extremely good for maintance. I have no idea if this gets you further, but I just want to share the ideas about remi.

The attached file contains the project including remi. Just unpack it somewhere.

Definitely like your single file version than the multi-file. I can't use a multi-file solution.

The amount of Remi code in PySimpleGUIWeb.py is tiny and I hope to keep it that way.

The design I want to implement is a screen that is completely replaced by another one with the ability to go back to the first screen.

I was hoping to get your first example running in repl.it. For some reason the browser doesn't automatically open to the page. It did run when I manually connected.

For reference, here is the Remi portion of the code:

    def remi_thread(self):
        logging.getLogger('remi').setLevel(logging.WARNING)
        logging.getLogger('remi').disabled = True
        logging.getLogger('remi.server.ws').disabled = True
        logging.getLogger('remi.server').disabled = True
        logging.getLogger('remi.request').disabled = True
        # use this code to start the application instead of the **start** call
        # s = remi.Server(self.MyApp, start=True, title=self.Title, address='0.0.0.0', port=8081, start_browser=True, userdata=(self,),  multiple_instance=False, update_interval=.001)

        # logging.getLogger('remi').setLevel(level=logging.CRITICAL)
        # logging.getLogger('remi').disabled = True
        # logging.disable(logging.CRITICAL)
        # s = remi.server.StandaloneServer(self.MyApp, width=1100, height=600)
        # s.start()
        Window.port_number += 1

        remi.start(self.MyApp, title=self.Title ,debug=False, address='0.0.0.0', port=0,  start_browser=True, update_interval=.00001, userdata=(self,))

        # remi.start(self.MyApp, title=self.Title ,debug=False,  userdata=(self,), standalone=True)  # standalone=True)

        # remi.start(self.MyApp, standalone=True, debug=True, userdata=(self,) )            # Can't do this because of a threading problem
        print('Returned from Remi Start command... now sending None event')

        # self.App.server_starter_instance._alive = False
        # self.App.server_starter_instance._sserver.shutdown()
        self.MessageQueue.put(None)     # if returned from start call, then the window has been destroyed and a None event should be generated


    class MyApp(remi.App):
        def __init__(self,*args):
            # self.window = window    # type:  Window
            # print(args[-1])
            userdata = args[-1].userdata
            self.window = userdata[0]       # type: Window
            self.window.App = self
            self.master_widget = None
            super(Window.MyApp, self).__init__(*args)

        def main(self, name='world'):
            # margin 0px auto allows to center the app to the screen
            self.master_widget = remi.gui.VBox()
            self.master_widget.style['justify-content'] = 'flex-start'
            self.master_widget.style['align-items'] = 'baseline'
            if self.window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT):
                self.master_widget.style['background-color'] = self.window.BackgroundColor
            try:
                PackFormIntoFrame(self.window, self.master_widget, self.window)
            except:
                print('* ERROR PACKING FORM *')
                print(traceback.format_exc())

            # add the following 3 lines to your app and the on_window_close method to make the console close automatically
            tag = remi.gui.Tag(_type='script')
            tag.add_child("javascript", """window.onunload=function(e){sendCallback('%s','%s');return "close?";};""" % (
            str(id(self)), "on_window_close"))
            self.master_widget.add_child("onunloadevent", tag)
            self.window.MessageQueue.put('Layout complete')     # signal the main code that the layout is all done
            self.window.master_widget = self.master_widget
            return self.master_widget          # returning the root widget


        def on_window_close(self):
            # here you can handle the unload
            # print("app closing")
            self.close()
            self.server.server_starter_instance._alive = False
            self.server.server_starter_instance._sserver.shutdown()
            self.window.MessageQueue.put(None)
            print("server stopped")

I did it!!

It's likely not nearly as sophisticated as your solution, but it's at least working.

I can now show a window AND a Popup window too.

import PySimpleGUIWeb as sg

layout = [
            [sg.Text('Your typed chars appear here:'), sg.Text('', key='_OUTPUT_')],
            [sg.Input(do_not_clear=True, key='_IN_')],
            [sg.Button('Show'), sg.Button('Exit'), sg.Button('Blank')]
         ]

window = sg.Window('Window Title').Layout(layout)

while True:             # Event Loop
    event, values = window.Read()
    print(event, values)
    if event is None or event == 'Exit':
        break
    if event == 'Show':
        sg.Popup('A popup!')

window.Close()

I'll quickly do a PyPI release so I can run it on repl.it

Repl.it actually added support for importing GitHub repos. But it's an experimental feature (you need to turn on the Explorer role in your account) and the PySimpleGUI repo breaks it.

I just did a release to PyPI, 0.8.0 and now my repl's are running so good. ;-(

It works! I dunno what was wrong with repl before, but it's working now:

https://repl.it/@PySimpleGUI/Popup-Demonstration

I am able to show a popup window inside of my main window's event loop!

The release to get these features is 0.8.0

I dunno if it'll work with more than 2 windows. We'll see.

The technique I used is very primitive. I was "hiding" the calling window by setting the widget's "hidden" attribute to "true". But I forgot to add it in to the code. It was a mistake to not have it in there...

BUT, it worked regardless and it makes sense why now.

When a second+ window is requested I build the window just like I did with the main window, using a VBox. Then I simply changed the window to point to the new window using the App.set_root_widget method. I appreciate the code that was submitted that showed me this critical function.

It has not been tested so I hope I didn't break anything in the process!

This opens up all sorts of things!

Closing as the basic request of a Remi port has been satisfied to say the least :-)

Thank you for the suggestion

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MikeTheWatchGuy picture MikeTheWatchGuy  路  3Comments

ihouses picture ihouses  路  6Comments

ECOM-Klaus picture ECOM-Klaus  路  4Comments

mozesa picture mozesa  路  5Comments

MikeTheWatchGuy picture MikeTheWatchGuy  路  6Comments