Open MikeTheWatchGuy opened 5 years ago
Having spent the day playing with Remi I think that I can do a PySimpleGUI port! I think the best approach will be to run Remi in a thread so that I can run the PySimpleGUI "event loop" in a non-blocking fashion. I'll let the thread block instead of the application.
This is actually pretty exciting! I hope to have a port up and running this week.
Hello @MikeTheWatchGuy ,
It would be cool to have Remi an alternative in PySImpleGUI. The remi event loop is not interruptable (as you maybe noticed) and can't terminate after a timeout. However AFAIK it should be the same for Qt.
I'm not sure to understand why you should stop the event loop. Be aware that the App.idle loop is not related to the event loop. Events are triggered by websocket actions.
How would you manage the access from multiple users?
Please be patient for the long delay between replies, I'm really busy, Have a good development Best Regards
Oh wow, I didn't expect this fast of a response! Thanks for your time.
I'm super-excited about this port! This is going to be so cool! You've really opened up a lot to Python programmers. I'll try to keep the requests for your time to a minimum. Hoping to have something up and running this week.
@MikeTheWatchGuy ok, keep us informed. Here I am for questions or suggestions. Thank you
@MikeTheWatchGuy Do you know that remi has a graphical gui editor? Here is a live example http://remiguieditor--daviderosa.repl.co/ (however I suggest to use it locally). Maybe you can use it to explore some remi widgets.
Thanks so much for offering to help.
I've got a question...
How can I pass in a parameter into the MyApp class? I need to provide access to a variable from within the methods of the MyApp class.
At the moment the MyApp class is self-contained, isolated using only the resources that are defined within the class. I hope this makes sense.
Oops... nevermind... just found a post where you discuss user data in the start call. Sorry to bug you...
Here is an example:
class MyApp(App):
def main(self, param1, param2, param3):
main_container = gui.VBox(width=300, height=200, style={'margin':'0px auto'})
main_container.append(gui.Label("PARAMETERS %s %s %s"%(param1, param2, param3)))
return main_container
if __name__ == "__main__":
# starts the webserver
start(MyApp, address='0.0.0.0', port=0, start_browser=True, userdata=('param1', 'param2', 3))
Poof! I'm up and running! PySimpleGUI code "running in a browser window". It's pretty extraordinary to see and experience. Never thought something like this would be possible.
I've released a super-early version to PyPI and GitHub as PySimpleGUIWeb.
You've made it really easy to do this. The Remi interface is easy to work with.
So far I have the Text, Input Text and Button "Elements" (PySimpleGUI widgets) up and running.
import PySimpleGUIWeb as sg
def main():
layout = [
[sg.Text('This is a text element')],
[sg.Text('Here is another ' ), sg.Text(' and another on the same line')],
[sg.Text('If you close the browser tab, the app will exit gracefully')],
[sg.InputText('Source'), sg.FolderBrowse()],
[sg.InputText('Dest'), sg.FolderBrowse()],
[sg.Ok(), sg.Cancel()]
]
window = sg.Window('Demo window..').Layout(layout)
while True:
event, values = window.Read()
print(event, values)
if event is None:
break
window.Close()
main()
print('Program terminating normally')
@MikeTheWatchGuy Do you know where can I download pysimpleguiweb??? Thank you
You can download the .py file from the GitHub or you can pip install it
pip install pysimpleguiweb
Be warned that it's super super early. I've only got enough going to show that it's going to work. I can display text, get text from the input fields, and read buttons. It's got a long ways to go (but progress should be quick). You could start with tkinter (or Qt or WxPython) and move over to this Remi port later. The PySimpleGUI source code is meant to be 100% compatible across the platforms.
Remi is an awesome framework to work with. It's making this super easy to do.
@MikeTheWatchGuy Thank you I am going to try it out soon ;)
I think it will be great if the user can use their own css files. So instead of super(Window.MyApp, self).init(args) on line 3113, I think we can do: try: super(MyApp, self).init(args, static_file_path={'res':'res'}) except: super(MyApp, self).init(*args)
A couple questions
What's the best way to turn off logging? It's working great and thus don't need the debug output. I set the level at INFO (and tried other levels) and I stilled get 3 messages.
Where is the best location for documentation? Things like what are all the parms to the start
call would be great.
Update.... (hope it's OK to update you on a few thing... you've really enabled some cool stuff.. without Remi none of this would be possible)
Having a ball working with the API. Today adding fonts, colors, window title, etc. It's so cool to see this stuff running in Chrome yet still executing my Python code.
I have a ton of demo programs that help me bring up ports. They require more and more features. I got this timer up an running. It does a lot of stuff like creating the window, updating the text very frequently, changing button text when the pause button clicked, closes the window when exit clicked. It's not bad for day 1 of the port.
@Brianzhengca let's keep the PySimpleGUI Issues discussed on the PySimpleGUI GitHub.
@MikeTheWatchGuy COOL! Unfortunately remi hasn't documentation yet.. Look at the code and the Readme file, or simply ask me. To solve the logging problem:
import remi.server as server
import logging
class MyApp(App):
#this is required to override the BaseHTTPRequestHandler logger
def log_message(self, *args, **kwargs):
pass
if __name__ == "__main__":
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 = server.Server(MyApp, start=True, address='0.0.0.0', port=8081, start_browser=True, multiple_instance=True)
Have a good code development
I've noticed that I no longer get exceptions when running my code. This is code that's running inside of the MyApp object.
Do I need to do something so that I see exceptions again?
I have write "perfect code" at the moment since I don't see any crashes.
Thank you for your help.
I will capture all of these things you are posting and put into a FAQ, readme, ..., something that you can use.
Hello @MikeTheWatchGuy ,
Instead logging.getLogger(...).disabled=True you can set the desired log level as you already know i.e. logging.getLogger('remi').setLevel(logging.WARNING). It should work as you expect.
Don't worry about making the FAQ for remi with these info, these will stay available in this issue/thread. ;-)
You've actually done a fantastic job of answering everyone's questions. There is a TON of stuff on gitter and a lot in these GitHub Issues. It just needs to be consolidated. The package is solid and it documents well.
For example, I just discovered how to set tooltips, another milestone, thanks to you answering a question. I'm simply keeping a list of these as I find them. I'll make a little document for myself and you're welcome to use it or not. While I don't enjoy docs, I see enormous value in them which is why PySimpleGUI is pretty heavily documented. I get comments, often, saying my package was chosen because of the documentation.
Managed to nail 3 more widgets last night (combobox, checkbox, listbox). Working my way through them all!
Today I added a multiline input and multiline output capabilities.
Is it possible to "Append" text to a multiline text widget? This for my scrolling text output.
At the moment the only thing I can think of doing is to keep track of the entire contents of the widget that I append new text onto and re-write the entire block of data into the widget. It's quite inefficient.
When I use only the new line (setLevel warning), I continue to get a bunch of log messages.
Even with a bunch of code
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
I still get 2 log messages.
127.0.0.1 - - [24/Jan/2019 12:46:28] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [24/Jan/2019 12:46:28] "GET /res:style.css HTTP/1.1" 200 -
I have a feature that returns events as elements are changed. If a character is typed into an input field, or a slider moved, etc. Your SDK is awesome to enabling this feature.
I am trying to get it working for an Input Text element. I get the callback just fine and the correct character is being passed in via the keycode parameter.
But, when I read the current "value" of the Input Text element it returns the initial value / previous value, not the new value.
For example, if my Input Text element is initialized with the value "Test" and I've enabled key stroke events, and then I highlight "Test" and replace it with "123". I get the keystroke callbacks for each of the characters 1, 2, 3. But, when I call widget.get_value() it returns "Test". It's only after I click a button that the get_value returns the new values.
@MikeTheWatchGuy Thank you a lot!
Logging problem: You maybe forgot to oveload the aforementioned method:
class MyApp(App):
#this is required to override the BaseHTTPRequestHandler logger
def log_message(self, *args, **kwargs):
pass
Text append: you should do something like:
mytextwidget.set_value( mytextwidget.get_value() + text_to_append )
get_value() returns the old text: onkeydown and onkeyup events doesn't directly update the widget value improving text consistency and preventing user interaction glitches. You however receives the updated value in the event parameters. onkeyup and onkeydown events provide the key and new_value.
I don't seem to be getting any exceptions when I have an error in my code. PyCharm isn't breaking like it normally would. Is there something special I need to do to enable exceptions again? It's difficult to debug without seeing where the problems are.
The keycode passed into the Input Text callback shows upper case characters. Maybe I'm converting the keycode incorrectly?
Is this a good way of getting the character?
character = char(int(keycode))
It seems like it's literally they KEY from the keyboard and that you have to know the status of the shift key to know what the actual typed character is. Surely I'm doing this wrong.
My "Spinner Element" that I provide through PySimpleGUI is text based. Instead of a range of numbers it's a list of strings. I can still do numeric entries this way as well as selecting strings.
In Qt I had to implement this as a combined spinbox and a text entry field. Is it possible to do something similar in Remi? Can I get just the up/down arrows and add my own text entry widget?
You've actually done quite a bit of docs. I found a PDF from December which was generated from the code best I can tell. I'm interested in doing something similar some day in PySimpleGUI so I want to study your code.
It will be helpful to add a FAQ / Common Operations document that consolidates the information you've so patiently posted all over the internet, especially on Gitter. I'm happy to collect up my notes and post them to help others following after me.
I see that Radio Buttons are absent. I think I could implement them using checkboxes, but it would be nice to change the graphic from a checkbox to a bullet.
How would you go about implementing a Radio Button capability? It doesn't have to be perfectly done. I'm interested in functionality over everything else.
I expect to keep asking questions as I continue my port. Should I continue to bulk them up into this single Issue or break them out into single issues that can be closed when we're doing with them? I expect they won't remain open for long. It gives up the ability to track requests individually as well as keep the conversation focused to a single topic. It will be helpful for future readers perhaps.
While I think it would be a good idea to post them individually, I don't want to create a feeling of being swamped with requests by posting 4 or 5 Issues at a time.
The way PySimpleGUI works it that pretty much every feature of Remi, tkinter, Qt, WxPython is wrapped in the PySimpleGUI SDK. I make all of the frameworks look and act the same way. It means I hit every widget with almost every option in a framework.
The size of your code base is amazingly small yet the feature list is huge. You use the Python language in a way I've not seen before. I'm not used to working with decorators so it's going to be a while before I really grasp what you're doing.
What I really like is the interfaces you've provided. I SO wish all of the other GUI packages made it as easy as you do to handle events. WxPython is probably the most similar to your interface. Qt would be next after that.
Exception problem "solved" by adding my own try/except around my code that's in the main
method. In the except portion I simply print the traceback:
print(traceback.format_exc())
This is exactly the kind of thing I'm putting into a FAQ since I'm guessing other people have asked the same question at some point.
@MikeTheWatchGuy here are the replies
you was unable to see exceptions because some logging sources are disabled in your code. I previously suggested you to replace logging.getLogger("...").disabled = True with logging.getLogger("...").setLevel(logging.WARNING). Here is an example of an App containing an exception, with a correct logging report:
import remi.gui as gui
from remi import start, App
import os
import logging
class MyApp(App):
def main(self):
#creating a container VBox type, vertical (you can use also HBox or Widget)
main_container = gui.VBox(width=300, height=200, style={'margin':'0px auto'})
main_container.onclick.connect()
# returning the root widget
return main_container
def log_message(self, *args, **kwargs):
pass
if __name__ == "__main__":
logging.getLogger('remi').setLevel(logging.WARNING)
logging.getLogger('remi.server.ws').setLevel(logging.WARNING)
logging.getLogger('remi.server').setLevel(logging.WARNING)
logging.getLogger('remi.request').setLevel(logging.WARNING)
# starts the webserver
start(MyApp, address='0.0.0.0', port=0, start_browser=True, username=None, password=None)
Keycode is always Uppercase for onkeyup and onkeydown events. I was not aware of this, but it's a standard for the browsers. However, since the base Widget already has these events with additional information (like the pressed key, not only the keycode), the solution is simple. We can override these events, copying the implementation from the Widget class:
import remi.gui as gui
from remi import start, App
import os
class TextInput_raw_onkeyup(gui.TextInput):
@gui.decorate_set_on_listener("(self, emitter, key, keycode, ctrl, shift, alt)")
@gui.decorate_event_js("""var params={};params['key']=event.key;
params['keycode']=(event.which||event.keyCode);
params['ctrl']=event.ctrlKey;
params['shift']=event.shiftKey;
params['alt']=event.altKey;
sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);
event.stopPropagation();event.preventDefault();return false;""")
def onkeyup(self, key, keycode, ctrl, shift, alt):
return (key, keycode, ctrl, shift, alt)
@gui.decorate_set_on_listener("(self, emitter, key, keycode, ctrl, shift, alt)")
@gui.decorate_event_js("""var params={};params['key']=event.key;
params['keycode']=(event.which||event.keyCode);
params['ctrl']=event.ctrlKey;
params['shift']=event.shiftKey;
params['alt']=event.altKey;
sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);
event.stopPropagation();event.preventDefault();return false;""")
def onkeydown(self, key, keycode, ctrl, shift, alt):
return (key, keycode, ctrl, shift, alt)
class MyApp(App):
def main(self):
main_container = gui.VBox(width=300, style={'margin':'0px auto'})
txt = TextInput_raw_onkeyup(True,'type here')
main_container.append(txt)
txt.onkeyup.connect(self.onkey)
self.lbl = gui.Label("", style={'font-size':'18px'})
main_container.append(self.lbl)
return main_container
def onkey(self, emitter, key, keycode, ctrl, shift, alt):
self.lbl.set_text("keyup keycode:" + keycode + " char:" + key)
if __name__ == "__main__":
start(MyApp, address='0.0.0.0', port=0, start_browser=True, username=None, password=None)
Here is a simple implementation:
class Spinner(gui.GridBox):
def __init__(self, choices, *args, **kwargs):
super(Spinner, self).__init__(*args, **kwargs)
self.index = 0
self.choices = choices
self.define_grid(['ab', 'ac'])
self.value = gui.Label("", width="100%", height="100%", style={'text-align':'center'}) #of course you can use a TextInput instead
self.bt_up = gui.Button("+", width="100%", height="100%") #you can also setup an Image with arrows
self.bt_down = gui.Button("-", width="100%", height="100%")
self.bt_up.onclick.connect(self.up)
self.bt_down.onclick.connect(self.down)
self.append({'a':self.value, 'b':self.bt_up, 'c':self.bt_down})
self.style.update({'grid-template-columns':'80% 20%', 'grid-template-rows':'50% 50%'})
self.up(None) #setting up the first value
self.attributes['tabindex'] ='0' #enables focusing
self.onkeyup.connect(self.onkey) #change value by keyboard arrows
def up(self, emitter):
self.index = max(0,self.index-1) #this prevents negative values
self.value.set_text(self.choices[self.index])
self.onchange()
def down(self, emitter):
self.index = min(len(self.choices)-1,self.index+1) #this prevents values outside choices len
self.value.set_text(self.choices[self.index])
self.onchange()
def onkey(self, emitter, key, keycode, ctrl, shift, alt):
if int(keycode)==38: #UP
self.up(emitter)
if int(keycode)==40: #DOWN
self.down(emitter)
@gui.decorate_event
def onchange(self):
return (self.choices[self.index],)
class MyApp(App):
def main(self):
main_container = gui.VBox(width=300, height=100, style={'margin':'0px auto'})
self.spinner = Spinner(['one', 'two', 'three'], width=200, height=30, style={'border':'1px solid gray'})
self.spinner.onchange.connect(self.spinner_changed)
main_container.append(self.spinner)
return main_container
def spinner_changed(self, emitter, value):
print("spinner changed %s"%value)
if __name__ == "__main__":
start(MyApp, address='0.0.0.0', port=0, start_browser=True, username=None, password=None)
I hope in the future I will be able to create some kind of documentation/tutorials
The best way to represent a radiobutton would be using Svg elements. You can try to implement a radiobutton widget as for the Spinner widget. If you find it difficult I will help you.
Single issue allows to mute it if not interesting for someone. Multiple issues allows more controls/order. There is another option, the reddit remi channel reddit.com/r/remigui , I think this would be the best solution. I suppose this would be the best solution.
Personally I prefer plain code over decorators. I prefer the code when it is easy to understand. However some things need particular solutions. If something is unclear, ask me, I will explain ;-)
It looks like Remi and PySimpleGUIWeb are tracking each other when it comes to Pip installs..
Remi installs are on the left, PySimpleGUIWeb on the right. What's a bit baffling is the installs on Jan 24th. There were over 1,000 installs of PySimpleGUIWeb but the Remi installs didn't go up. This was prior to adding Remi as a requirement on PyPI. Remi had to be manually installed at that time. Now Remi is automatically installed.
so much donwloads @MikeTheWatchGuy :D but however some of them are automatic pip installs from repl.it .
Damn, we really hit a high mark over the past few days! Even if some of this is repl.it traffic, it's really impressive!
It's been suggested that PySimpleGUI be ported to run on top of Remi.
PySimpleGUI has been ported to tkinter, Qt, and WxPython. Sure would be fun to run it on Remi too.
Is this something that you think is possible?
The architecture of PySimpleGUI's "event loop" needs to be able to call the underlying GUI framework's "mainloop" in both a non-blocking way and have the ability to "cancel" a mainloop after a set amount of time is elapsed. Or, if it's possible to put a "timeout" on the mainloop then a timer is not needed.
The Architecture is discussed here.
Can Remi provide these services?
I'm sorry that I have not yet done much development in Remi.... still learning and want to learn more. Hope to be able to learn the answers to these questions myself, but would be helpful to get a jump start if possible.