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
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..
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.
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.
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'].
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
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.