micropython-micro-gui
micropython-micro-gui copied to clipboard
Why that trash symbol?
Hello @peterhinch
I'm using micro-gui
with the ssd1306
mono display (128 x 64
) with three buttons, but the micro-gui
is showing in all demos a strange symbol (like as a trash) on the top right side. Why that symbol and how to remove?
Here a screenshot showing that trash symbol inside the green circle using demo/simple.py
:
As I'm using a mono display I just changed the simple.py
:
- All places where has
CWriter
toWriter
-
wri = CWriter(ssd, arial10, GREEN, BLACK)
towri = Writer(ssd, arial10)
gui/demos/simple.py:
# simple.py Minimal micro-gui demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
# verbose default indicates if fast rendering is enabled
wri = Writer(ssd, arial10)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 50
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Simple demo: button presses print to REPL.')
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()
Output:
$ mpremote run simple.py
Using 3 switches.
Simple demo: button presses print to REPL.
Orientation: Horizontal. Reversal: False. Width: 128. Height: 64.
Start row = 0 col = 0
Warning: attempt to create Button outside screen dimensions.
Warning: attempt to create Button outside screen dimensions.
My hardware_setup.py:
from machine import Pin, SPI
import machine
import gc
import time
from ssd1306 import SSD1306_I2C as SSD
WIDTH = const(128)
HEIGHT = const(64)
i2c = machine.SoftI2C(scl=machine.Pin(18), sda=machine.Pin(17))
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(WIDTH, HEIGHT, i2c)
from gui.core.ugui import Display
# Define control buttons
nxt = Pin(42, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(12, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(41, Pin.IN, Pin.PULL_UP) # Move to previous control
display = Display(ssd, nxt, sel, prev) # 3-button mode
The ssd1306.py
is from official micropython driver.
Thank you!
I tested with font6.py
(and others fonts) on the demo/simple.py
and I have same result
import gui.fonts.font6 as font6
wri = Writer(ssd, font6)
As you know, I was using the nano-gui and I was running nano-gui
demos in the same display and that symbol never showed.
Ohh, I realized that symbol is the exit button, right? So sorry!
Is there how do not use and do not show that exit button? Because that exit button use a very large space on the display when used little display as the ssd1306
with 128x64 pixels.
Let me to do another question: on the nano-gui
I was using the demo/mono_test.py
. I would like to use that example in the micro-gui
, where Next/Prev buttons change between fields()
, multi_fields()
and meter()
. How is better way to do that?
Thank you in advance!
I think the micro-gui
is no adapted very well to little display, like as 128x64, am I correct or not?
Mostly micro-gui
demos the widgets go to out of display or I have error ValueError: row is out of range
or showed message to use a display with at least 320x240 pixels. So, maybe is a good way use micro-gui
working with Next
/Prev
between entire Screen, like as in the fields()
, multi_fields()
and meter()
and if Select
is pressed on the multi_fields()
for example, another Screen can be called, or some thing like that - any idea or example that I can run as a start point?
The symbol is the CloseButton
: just remove that line to get rid of it.
micro-gui
is not optimisd for such a tiny screen, and I've never investigated the sort of modes you have in mind.
The symbol is the
CloseButton
: just remove that line to get rid of it.
Sorry for that dummy question. Now I see in the buttons.py
code:
# Preferred way to close a screen or dialog. Produces an X button at the top RHS.
# Note that if the bottom screen is closed, the application terminates.
class CloseButton(Button):
micro-gui
is not optimisd for such a tiny screen, and I've never investigated the sort of modes you have in mind.
Now I have three buttons on the display to use inputs Prev, Select, Next, but as the menus e etc of micro-gui are very large to that 128x64 display, I'm in a dillema what to do:
- Back to nano-gui (that is optimised to tiny display) and continue with re-invente the inputs Prev, Select and Next (now that I have three buttons on hardware). In that examples that I was testing in nano-gui I was already working changing of Screen.
- Continue on micro-gui and find a way (any idea?) to not use the standard menus (that are very large for 128x64 display). Has the micro-gui something that I can just use Prev and Next to change of Screen/Window? and use the Select on that Screen to show another Screen/Window?
Ps:
- As is a tiny display the idea is not to use widgets like as select, dropbox, etc, but just show the informations in each Screen and a way to change Screens with Next, Prev and Select
- For now, as is a very tiny display I do not need to change configurations, just read information and execute some commands (that is the use for Select), like as enable/disable WiFi AP mode, or a INPUT/OUTPUT, etc.
- If I choose the option 2 (micro-gui), the advantage is if in the feature I need to configure only few things, I'm already using micro-gui that support config. If I choose the option 1 (nano-gui) and I need to configure something I think that is possible run micro-gui together nano-gui just to configure that few things.
Below are two prints using nano-gui with that re-inveted inputs next, prev, select that I opened that issue in the nano-gui project: https://github.com/peterhinch/micropython-nano-gui/issues/53
Is just two Screens, but will be much more, and the idea is prev/next change of that complete screen to another and use Select to change of Screen too.
Could you please give your thoughts about that?
Thank you
Here is the general approach I would use. Use micro-gui. Assign 3 unused pins to Button objects and use them to initialise the GUI. Their job is purely to allow the GUI to start correctly.
Assign the three pins used by your physical buttons to Button objects: let's call those Next, Prev and Sel. Note that Button instances allow you to dynamically assign callbacks.
Examine the demos to see how next and prev virtual buttons work. On your first screen, define an after_open
method which assigns a callback to your Next button*. On the next screen, after_open
would assign callbacks to Next, Prev and Sel. You'll need to be sure that every screen has an after_open
which assigns appropriate callbacks to the three buttons.
*On the first screen you'll also need to assign callbacks to Prev and Sel, because you might run this screen in response to Prev from the next. You can also disable the callback - see docs.
I'm convinced this could work, but it will require careful coding and studying the screen change mechanism.
Hello @peterhinch I liked so much about your idea and I understood your rationale. I read many docs and source code as well from demos
and ugui.py
, but I have still some difficult to put that idea to works
Here is the general approach I would use. Use micro-gui. Assign 3 unused pins to Button objects and use them to initialise the GUI. Their job is purely to allow the GUI to start correctly.
Just to clarify: I already has 3 Pins dedicated for the physical Buttons, so need I more 3 Pins (even temporary Pins) to initialize the GUI? If yes, I have no more Pins unused in my board, the last two unused pins I used to complete 3 physical Buttons instead 1 Button. But, if is really necessary, I can to start the display part of code before others async applications, so I use that 3 pins in temporary mode from that others applications until GUI will be started, and after remap that 3 Pins again to others applications.
Assign the three pins used by your physical buttons to Button objects: let's call those
Next
,Prev
andSel
. Note that Button instances allow you to dynamically assign callbacks.
I not understand how to that in code, because the hardware_setup.py
already has that 3 Pins configured as Prev
, Sel
and Next
Examine the demos to see how next and prev virtual buttons work. On your first screen, define an
after_open
method which assigns a callback to your Next button*. On the next screen,after_open
would assign callbacks to Next, Prev and Sel. You'll need to be sure that every screen has anafter_open
which assigns appropriate callbacks to the three buttons.*On the first screen you'll also need to assign callbacks to Prev and Sel, because you might run this screen in response to Prev from the next. You can also disable the callback - see docs.
I understand very well about the after_open(self). I did some tests with after_open(self)
while I was porting (with success :partying_face: ) my Screens from nano-gui
to micro-gui
and It's simple: just execute something after widgets are created on the Screen. So after each Screen start, I need on the after_open(self)
to assign a new Screen for the Next
, Prev
and Sel
- but how to do that (what code)?
I examined demo simple.py
that has two buttons where is possible test the Prev
and Next
mechanism, but inside the simple.py
I think that mechanismi is not showed, right? So I checked in the ugui.py
where has class Display(DisplayIP):
that is used to assign that 3 buttons on hardware_setup
to the 3 real pins. There is the class Input:
maybe is there (I think) where I need to reuse some variables to change the Prev
, Sel
and Next
on the after_open(self)
? Sorry, that is too much information for a newbie in GUI.
I'm convinced this could work, but it will require careful coding and studying the screen change mechanism.
I don't know if I'm asking too much, but honestly I need your help for an example to start. So, could you please, create a very simple example with two or 3 Screens implementing that idea to me as a start point? And maybe this example can be a official example
for anyone that want to use micro-gui
with Prev
, Sel
, Next
moving over Screens (when tiny display) instead to use the menus and some others widgets that are larger too much for that tiny displays.
Thank you so much!
At least I'm glad today that I have ported successfully that two nano-gui
Screens to micro-gui
(bellow the code):
multi_fields.py:
# simple.py Minimal micro-gui demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton, Meter
from gui.core.writer import Writer
import uasyncio as asyncio
# Font for CWriter
import gui.fonts.arial10 as arial10
import gui.fonts.font6 as small
from gui.core.colors import *
def xorshift64star(modulo, seed = 0xf9ac6ba4):
x = seed
def func():
nonlocal x
x ^= x >> 12
x ^= ((x << 25) & 0xffffffffffffffff) # modulo 2**64
x ^= x >> 27
return (x * 0x2545F4914F6CDD1D) % modulo
return func
async def change_multi_fields(nfields):
random = xorshift64star(2**24 - 1)
while True:
for field in nfields:
value = random() / 16777
field.value('{:5.2f}'.format(value))
await asyncio.sleep_ms(500)
class BaseScreen(Screen):
def __init__(self):
super().__init__()
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it
wri = Writer(ssd, small, verbose=False)
wri.set_clip(False, False, False)
nfields = []
dy = small.height() + 6
y = 2
col = 15
width = wri.stringlen('999.99')
for txt in ('1:', '2:', '3:'):
Label(wri, y, 0, txt)
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border
y += dy
dy = small.height() + 6
y = 2
col = 82
for txt in ('4:', '5:', '6:'):
Label(wri, y, 67, txt)
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border
y += dy
self.reg_task(change_multi_fields(nfields))
CloseButton(wri)
def after_open(self):
print('----------------------------- 1')
ssd.hline(60, 60, 30, BLUE)
ssd.vline(67, 0, 28, BLUE)
print('----------------------------- 2')
def test():
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()
meter1.py
# simple.py Minimal micro-gui demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton, Meter
from gui.core.writer import Writer
import uasyncio as asyncio
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
async def change_meter(m0, m1, m2):
steps = 10
c = 1
for n in range(steps + 1):
m0.value(c/steps)
m1.value(n/steps)
m2.value(1 - n/steps)
await asyncio.sleep(1)
c += 1
class BaseScreen(Screen):
def __init__(self):
super().__init__()
wri = Writer(ssd, arial10, verbose=False)
m0 = Meter(wri, 5, 2, height = 45, divisions = 4, legends=('0.0', '50', '100', '245!', '250!'), label='4', style=Meter.BAR)
m1 = Meter(wri, 5, 44, height = 45, divisions = 4, legends=('0.0', '50', '200', '295!', '300!'), label='5', style=Meter.BAR)
m2 = Meter(wri, 5, 86, height = 45, divisions = 4, legends=('0.0', '50', '200', '335!', '340!'), label='6', style=Meter.BAR)
self.reg_task(change_meter(m0, m1, m2))
CloseButton(wri)
def after_open(self):
print('----------------------------- 1')
ssd.hline(60, 60, 30, BLUE)
ssd.vline(67, 0, 28, BLUE)
print('----------------------------- 2')
def test():
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()
Hi @peterhinch Sorry for one more time request, but I would like to know if will be possible to you to create a very simple example with two or 3 Screens implementing that your idea as a start point to me? A good and perfect example with three screens could be that my two screens above and the third scree the simple.py example, because the simple.py is a perfect screen to execute a command (it has button yes and no) to enable/disable some feature. However as the simple.py has real (not more virtual) buttons (yes and no) maybe need to do a additional remap of pins in the after_open(), and this is another thing that I have no idea how to implement, and that's why it's so important an example with those three screens as start point :)
Don't worry if you can't to do that, is just to me know what way I will follow. Unfortunately I was no capable to implement your idea using micro-gui, and if you can't to create an example, I will back to nano-gui and and continue with re-invente the inputs Prev, Select and Next.
Thank you in advance!
I'm sorry but I am far too busy on another project to write and test a demo for you.
I am convinced that micro-gui is the best, and simplest, approach. Looking at simple.py
you would have two Pushbutton
objects associated with your physical buttons. The Pushbutton
close callbacks would be the callback currently associated with the Button
objects on the screen.
In the case where screens change, the Pushbutton
callbacks might need to change. This would be done in the after_open
method.
I am convinced that micro-gui is the best, and simplest, approach.
Me too. And I did read this messages bellow dozens of times and still have questions.
Looking at
simple.py
you would have twoPushbutton
objects associated with your physical buttons.
The two Pushbutton
that you are talking about are the Button() yes
on line 32 and Button() no
on line 34 of the file simple.py right?
The
Pushbutton
close callbacks would be the callback currently associated with theButton
objects on the screen.
Now you talk about again of the same Pushbutton
that I believe to be the same on line 32 and 34. If yes, that Button() do not have close callbacks. They have callback, but that call just the my_callback()
on line 22.
And you talk about "associated with the Button
objects on the screen", but If Pushbutton
are objects (line 32, 34) what line are there the Button
objects?
In the case where screens change, the
Pushbutton
callbacks might need to change. This would be done in theafter_open
method.
Here I'm confused too because I do not know what lines in the simple.py arePushbutton
objects and what are the Button
objects.
Could you please clarify that?
Other question: for that solution need I to change some code in some file inside core/ or widgets/ directories, or all the changes needed is just on the simple.py for example?
I'm sorry but I am far too busy on another project to write and test a demo for you.
I agree, but if you could at least write directly here in the GitHub a piece of code as a draft (without any test) I can get better the idea and I will to do the tests and complete what will need.
Thank you so much!
Button
objects are on-screen widgets. Pushbutton
objects are physical pushbuttons.
In your final application you won't have any Button
instances. What I am suggesting is that you implement Pushbutton
instances for your electrical buttons. You study the examples to see how the callbacks for Button
s work. And you modify the example code so that the callbacks are assigned to the Pushbutton
close
instead of the Button
. The Button
instances can then be eliminated from the screen.
I had progress.. :partying_face: I have one Question below.
In the hardware_setup.py
I used unused pins to initialize gui without errors. - Done!
Button
objects are on-screen widgets.Pushbutton
objects are physical pushbuttons.
Got it!
In your final application you won't have any
Button
instances.
Got it!
What I am suggesting is that you implement
Pushbutton
instances for your electrical buttons.
Done on simple.py
:
from gui.primitives import Pushbutton
from machine import Pin
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', button, arg)
super().__init__()
# verbose default indicates if fast rendering is enabled
wri = Writer(ssd, arial10)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 50
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
# 3 Pushbuttons implemented (prev, sel, next)
prev = Pin(41, Pin.IN, Pin.PULL_UP)
self.btn_prev = Pushbutton(prev)
sel = Pin(12, Pin.IN, Pin.PULL_UP)
self.btn_sel = Pushbutton(sel)
nxt = Pin(42, Pin.IN, Pin.PULL_UP)
self.btn_nxt = Pushbutton(nxt)
# test to implement callback on the pushbutton
#self.btn_nxt = Pushbutton(b_next, callback=my_callback, args=('Test',))
You study the examples to see how the callbacks for
Button
s work.
This is in progress yet... I'm stuying!
And you modify the example code so that the callbacks are assigned to the
Pushbutton
close
instead of theButton
.
Question: here I got the idea too, but the /gui/primitives/pushbutton.py
do not has the close
method. What or where is that close
on the Pushbutton
?
Any additional comments about the code above (including that line commented about callback on the pushbutton) is wellcome!
Thank you!
See the Pushbutton docs. You use the press_func
method to assign a callback.
Hey @peterhinch It works like you told me! :partying_face: :balloon: Thank you for your support and especially for your patience!
I still testing and finding the best way to call each Screen in the after_open(self)
I'm thinking to have a python file for each Screen and import each Python file when I will use it in the callback to sel, next or prev. Any suggestion?
A question about this error: ValueError: Screen has no active widgets.
Is possible to do something to not show this error, or to create a invisible active widget? Because sometimes I do not need active widgets. For now I'm using the CloseButton(wri) to works.
Thank you very much!
I think importing on demand will work. If you want a modular design this is fine, but this way of working won't save resources. The GUI only instantiates the Screen
instance when it is opened, and MicroPython's garbage collection will retrieve the RAM when it is closed.
Re the error, I hadn't thought of this. I think suppressing the error will only move the problem elsewhere - the GUI is built on the assumption that there is at least one active control on a screen. So the best solution is probably to make the close button invisible. Adding the following line to widgets\buttons.py
should do this:
class CloseButton(Button):
def __init__(self, writer, width=0, callback=dolittle, args=(), bgcolor=RED):
scr = Screen.current_screen
# Calculate the button width if not provided. Button allows
# 5 pixels either side.
wd = width if width else (writer.stringlen('X') + 10)
self.user_cb = callback
self.user_args = args
super().__init__(writer, *scr.locn(4, scr.width - wd - 4),
width = wd, height = wd, bgcolor = bgcolor,
callback = self.cb, text = 'X')
self.visible = False # Add this line
Hi @peterhinch works with self.visible = False
Mostly Screens that I will have is just to user see the data, like as that Screens I posted above (meter, multifields), but some Screen I want a Screen exactely as the simple.py
with that two active buttons (yes and no
), where user can for example Enable AP mode
and choose Yes or No
. So I was thinking (and I already did some tests) where in the hardware_setup.py
I can to use the correct pins, I mean, the same used in the pushbuttons on the Screens. So when is a Screen where there is no active widgets that pins on the hardware_setup.py
will do not effect, but when I open a window like as the simple.py
that hardware_setup.py
pins will works automatically, and, I in this screen I do not configure the next/prev/sel
on the after_open()
, but only when user choose yes or not
. Does that make sense for you?
Thank you in advance!
That sounds OK.
Hello @peterhinch What's the correct way to integrate my micro-gui app with other async tasks/servers?
That integrate below works, but I note that after I integrate that very simple example (the simple.py) all my system got very slow.
Ps: I changed on the /gui/core/ugui.py
from do_gc = True
to do_gc = False
but do not solve the problem :(
Any idea why integrate the example simple.py
got all system slow?
main.py:
async def main():
await start._setup()
# Start ModBus Slave TCP
slave_tcp_task = _start_slave_tcp('192.168.43.143', 502, 2)
asyncio.run(slave_tcp_task)
# Start micro-gui simple.py
print('starting display... ---------------------')
from main_simple import test
t1 = asyncio.create_task(test())
print('Display started! ---------------------')
# Start Microdot
await app.start_server(host='0.0.0.0', port=80, debug=False)
try:
asyncio.run(main())
except KeyboardInterrupt:
print('Stopped')
main_simple.py:
import hardware_setup # Create a display instance
from gui.core.ugui import Screen
from simple import BaseScreen
async def test():
print('Simple demo: button presses print to REPL.')
Screen.change(BaseScreen) # A class is passed here, not an instance.
#test()
simple.py:
# simple.py Minimal micro-gui demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
# Font for Writer
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
# verbose default indicates if fast rendering is enabled
wri = Writer(ssd, arial10)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 50
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
#def test():
# print('Simple demo: button presses print to REPL.')
# Screen.change(BaseScreen) # A class is passed here, not an instance.
#
#test()
EDIT: my ESP32-S3 is clocked at full speed: 240 MHz
micro-gui
automatically starts uasyncio
- the intention being for it to be accessible by means of synchronous coding only.
Applications therefore shouldn't use asyncio.run()
. The way to run asynchronous code within a micro-gui
application is to launch tasks from a callback (e.g. after_open
) or from the base screen's __init__.py
- see for example the aclock.py demo. See the docs for a way to ensure tasks are cancelled on exit.
Hello @peterhinch
micro-gui
automatically startsuasyncio
- the intention being for it to be accessible by means of synchronous coding only.
Yes, I see that in the docs. But please, think in a very simple scenario, if I use Microdot (or/and other servers) I need to start the micro-gui
in asyncronous
mode, otherwise the micro-gui
will block all async applications, that's why I started the micro-gui
application as a task - t1 = asyncio.create_task(test_display())
Let me see if I understood what you are saying: software that need to run the micro-gui
, need to have micro-gui
as the main (sync) started application and the async servers/tasks
need only be started from the micro-gui using the self.reg_task()
?
Applications therefore shouldn't use
asyncio.run()
. The way to run asynchronous code within amicro-gui
application is to launch tasks from a callback (e.g.after_open
) or from the base screen's__init__.py
- see for example the aclock.py demo. See the docs for a way to ensure tasks are cancelled on exit.
Yes, I already following that example (for the self.reg_task()
) when I ported the nano-gui examples to micro-gui - I pasted this thread above.
But I can't see how I can to start all async servers
like Microdot and others from the self.reg_task()
, any example as start Microdot for example?
- Is a good idea to support the
micro-gui
to be started from anasync code
? Because I can to start all myasync servers
correctly as their docs say and startmicro-gui
as a task! - Starting
micro-gui
in async mode (as a task - as my example above) apparently works (t1 = asyncio.create_task(test_display())
), the problem is just that all applications got slow. But if the applications do not got to slow, can I use in that way? I'm asking this because I found that if I change the lineawait asyncio.sleep_ms(0) # Let user code respond to event
in the /core/ugui.py fromawait asyncio.sleep_ms(0)
toawait asyncio.sleep_ms(50)
all system works perfect - not got slow anymore. Is necessary to be0
in that sleep or can it be changed to other value, like as50
? Or I will have some problem in the future? I not understand very well, but I think thatsleep_ms(0)
means that refresh data on the display will be done as soon is possible, but if I usesleep_ms(50)
will refresh only each50
ms, is that correct?
Thank you very much
EDIT:
About the Item 2.
above: I changed to asyncio.sleep_ms(100)
to have better results, because 50ms was still a bit slow.
@peterhinch There is a situation with self.visible = False # Add this line
on the /gui/widgets/buttons.py
at the class CloseButton(Button)
. It works (CloseButton
is invible now), but that Screen stop to call the NEXT Screen configured in the after_open(self)
If I comment line #self.visible = False # Add this line
the NEXT
back to works, I mean, the next Screen
now show when I click in the NEXT pushbutton
CloseButton(wri)
def next_screen(self, arg):
print('Pushbutton pressed', arg)
Screen.change(ScreenTempMeter01)
def after_open(self):
nxt = Pin(42, Pin.IN, Pin.PULL_UP)
self.btn_nxt = Pushbutton(nxt)
self.btn_nxt.press_func(self.next_screen, ('next screen',))
@peterhinch one more question: how is possible to pass params
to the class BaseScreen(Screen):
?
I'm using the simple.py as reference.
I added a msg
param this in the init:
def __init__(self, msg=None):
print(msg)
And where the Screen is called I changed from Screen.change(BaseScreen)
to Screen.change(BaseScreen(msg='test1'))
but I have this error: ValueError: Must pass Screen class or subclass (not instance)
. Yes, I understand that it accept just class, and not an instance. So, what is the best way to pass params
to be used inside the BaseScreen
code?
simple.py changed:
# simple.py Minimal micro-gui demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
# Font for Writer
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self, msg=None):
print(msg)
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
# verbose default indicates if fast rendering is enabled
wri = Writer(ssd, arial10)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 50
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Simple demo: button presses print to REPL.')
Screen.change(BaseScreen(msg='test1')) # A class is passed here, not an instance.
test()
Thank you again :)
Please see the docs. You must pass the args to the Screen.change()
method and they will be picked up by BaseScreen.__init__
.
Screen.change(BaseScreen, msg='test1')
The code may be seen here
@peterhinch The args works - thanks :)
The proof of concept about your pushbutton
idea works, but I have two problems and one question.
I did a very very simple code - it has screen-1, screen-2, screen-3, and screen-4
, where pushbutton
prev
and next
change (forward or backwards) between screen-1
, screen-2
and the screen-3
. The screen-4
is just changed when in the screen-3
user click in the sel
pushbutton
. And to go out from screen-4
user need just to press Button
Yes
- so, that will back to screen-1
. The code is pasted below, but I attached in tar.gz
format the files to be easy to test. There is attached as well a video with the idea running and showing the problem-2
Could you please to check my code if there is something wrong? I did a very simple code because if you want to test, you need just to call the screen_main.py
Problems:
-
self.visible = False
on theclass CloseButton(Button)
works,CloseButton
turn on invisible, but that stop to work theprev
,sel
and thenext
of thepushbuttons
(to change the screens). Any idea how to fix that? I pasted details about this problem in this thread The example below (including the video) are usingCloseButton
visible, otherwise will not works the idea. - When I press
sel
on Screen-3 to change to Screen-4 (that hasButton
Yes
andNo
) , automaticallyButton
Yes
is pressed - But I do not pressed anything. Do you know what is the problem?
Question: I'm changing to new Screen
from inside another Screen (button.press_func()
), so every time that a Screen is changed to another, the previous Screen
will not closed and never will. I checked that if I close (click in the CloseButton
) the Screen
, that Screen is closed and back to last Screen and so on, until all Screens
are closed. So, using this idea (like as a carousel - without CloseButton
) the same Screens
will be called many times and they will never close. Can that be a problem? The same question is for when used self.reg_task()
in the Screens
- since the screens will never close, each time that Screen
will be called, a new self.reg_task()
will be executed. This code is not using self.reg_task()
, because is just a Proof of Concept, but I will use it.
screen_main.py:
import hardware_setup # Create a display instance
from gui.core.ugui import Screen
from gui.primitives import Pushbutton
from machine import Pin
from screen_1 import BaseScreen_1
from screen_2 import BaseScreen_2
from screen_3 import BaseScreen_3
from screen_4 import BaseScreen_4
screens = {}
screens['BaseScreen_1'] = BaseScreen_1
screens['BaseScreen_2'] = BaseScreen_2
screens['BaseScreen_3'] = BaseScreen_3
screens['BaseScreen_4'] = BaseScreen_4
prev_pin = Pin(41, Pin.IN, Pin.PULL_UP)
sel_pin = Pin(12, Pin.IN, Pin.PULL_UP)
next_pin = Pin(42, Pin.IN, Pin.PULL_UP)
prev_btn = Pushbutton(prev_pin)
sel_btn = Pushbutton(sel_pin)
next_btn = Pushbutton(next_pin)
buttons = {}
buttons['prev'] = prev_btn
buttons['sel'] = sel_btn
buttons['next'] = next_btn
def main_screen():
params = {'screens': screens, 'buttons': buttons}
kwargs = {'params': params}
Screen.change(screens['BaseScreen_1'], kwargs=kwargs)
main_screen()
screen_1.py:
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
import gui.fonts.arial10 as arial10
class BaseScreen_1(Screen):
def __init__(self, params):
self.kwargs = {'params': params}
self.params = params
self.screens = params['screens']
self.prev_btn = params['buttons']['prev']
self.sel_btn = params['buttons']['sel']
self.next_btn = params['buttons']['next']
super().__init__()
wri = Writer(ssd, arial10, verbose=False)
col = 2
row = 2
screen_name = 'Screen-1'
print(f'I am screen: {screen_name}')
Label(wri, row, col, screen_name)
CloseButton(wri)
def prev_screen(self, arg):
screen = self.screens['BaseScreen_3']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def sel_screen(self, arg):
print(f'Pushbutton pressed: {arg}')
print(f'This Screen has no function to the: {arg}')
def next_screen(self, arg):
screen = self.screens['BaseScreen_2']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def after_open(self):
self.prev_btn.press_func(self.prev_screen, ('prev screen',))
self.sel_btn.press_func(self.sel_screen, ('sel screen',))
self.next_btn.press_func(self.next_screen, ('next screen',))
screen_2.py:
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
import gui.fonts.arial10 as arial10
class BaseScreen_2(Screen):
def __init__(self, params):
self.kwargs = {'params': params}
self.params = params
self.screens = params['screens']
self.prev_btn = params['buttons']['prev']
self.sel_btn = params['buttons']['sel']
self.next_btn = params['buttons']['next']
super().__init__()
wri = Writer(ssd, arial10, verbose=False)
col = 2
row = 2
screen_name = 'Screen-2'
print(f'I am screen: {screen_name}')
Label(wri, row, col, screen_name)
CloseButton(wri)
def prev_screen(self, arg):
screen = self.screens['BaseScreen_1']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def sel_screen(self, arg):
print(f'Pushbutton pressed: {arg}')
print(f'This Screen has no function to the: {arg}')
def next_screen(self, arg):
screen = self.screens['BaseScreen_3']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def after_open(self):
self.prev_btn.press_func(self.prev_screen, ('prev screen',))
self.sel_btn.press_func(self.sel_screen, ('sel screen',))
self.next_btn.press_func(self.next_screen, ('next screen',))
screen_3.py:
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
import gui.fonts.arial10 as arial10
class BaseScreen_3(Screen):
def __init__(self, params):
self.kwargs = {'params': params}
self.params = params
self.screens = params['screens']
self.prev_btn = params['buttons']['prev']
self.sel_btn = params['buttons']['sel']
self.next_btn = params['buttons']['next']
super().__init__()
wri = Writer(ssd, arial10, verbose=False)
col = 2
row = 2
screen_name = 'Screen-3'
print(f'I am screen: {screen_name}')
Label(wri, row, col, screen_name)
col = 2
row = 20
Label(wri, row, col, 'Press *SEL* to open')
col = 2
row = 40
Label(wri, row, col, 'the Screen-4')
CloseButton(wri)
def prev_screen(self, arg):
screen = self.screens['BaseScreen_2']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def sel_screen(self, arg):
screen = self.screens['BaseScreen_4']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def next_screen(self, arg):
screen = self.screens['BaseScreen_1']
print(f'Pushbutton pressed: {arg}. Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
def after_open(self):
self.prev_btn.press_func(self.prev_screen, ('prev screen',))
self.sel_btn.press_func(self.sel_screen, ('sel screen',))
self.next_btn.press_func(self.next_screen, ('next screen',))
screen_4.py:
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import Writer
import gui.fonts.arial10 as arial10
class BaseScreen_4(Screen):
def __init__(self, params):
self.kwargs = {'params': params}
self.params = params
self.screens = params['screens']
self.prev_btn = params['buttons']['prev']
self.sel_btn = params['buttons']['sel']
self.next_btn = params['buttons']['next']
def my_callback(button, arg):
print('Button pressed', arg)
if arg == 'Yes':
screen = self.screens['BaseScreen_1']
print(f'Your choose was *{arg}*, so Changing to the screen: {screen}')
Screen.change(screen, kwargs=self.kwargs)
super().__init__()
wri = Writer(ssd, arial10, verbose=False)
col = 2
row = 2
screen_name = 'Screen-4'
print(f'I am screen: {screen_name}')
Label(wri, row, col, screen_name)
col = 2
row = 14
Label(wri, row, col, 'Press Yes')
col = 2
row = 28
Label(wri, row, col, 'to go to Screen-1')
row = 40
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 50
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def prev_screen(self, arg):
print(f'Pushbutton pressed: {arg}')
print(f'This Screen has no function to the: {arg}')
def sel_screen(self, arg):
print(f'Pushbutton pressed: {arg}')
print(f'This Screen has no function to the: {arg}')
def next_screen(self, arg):
print(f'Pushbutton pressed: {arg}')
print(f'This Screen has no function to the: {arg}')
def after_open(self):
self.prev_btn.press_func(self.prev_screen, ('prev screen',))
self.sel_btn.press_func(self.sel_screen, ('sel screen',))
self.next_btn.press_func(self.next_screen, ('next screen',))
hardware_setup.py
from machine import Pin, SPI
import machine
import gc
import time
from ssd1306 import SSD1306_I2C as SSD
WIDTH = const(128)
HEIGHT = const(64)
i2c = machine.SoftI2C(scl=machine.Pin(18), sda=machine.Pin(17))
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(WIDTH, HEIGHT, i2c)
from gui.core.ugui import Display
# Define control buttons
nxt = Pin(42, Pin.IN, Pin.PULL_UP) # Move to next control
#nxt = Pin(10, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(12, Pin.IN, Pin.PULL_UP) # Operate current control
#sel = Pin(11, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(41, Pin.IN, Pin.PULL_UP) # Move to previous control
#prev = Pin(14, Pin.IN, Pin.PULL_UP) # Move to previous control
display = Display(ssd, nxt, sel, prev) # 3-button mode
Output: this is the same output that show in the video (attached):
$ mpremote run screen_main.py
Using 3 switches.
I am screen: Screen-1
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_2'>
I am screen: Screen-2
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_3'>
I am screen: Screen-3
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_1'>
I am screen: Screen-1
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_2'>
I am screen: Screen-2
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_3'>
I am screen: Screen-3
Pushbutton pressed: sel screen. Changing to the screen: <class 'BaseScreen_4'>
I am screen: Screen-4
Button pressed Yes
Your choose was *Yes*, so Changing to the screen: <class 'BaseScreen_1'>
I am screen: Screen-1
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_2'>
I am screen: Screen-2
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_3'>
I am screen: Screen-3
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_1'>
I am screen: Screen-1
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_2'>
I am screen: Screen-2
Pushbutton pressed: next screen. Changing to the screen: <class 'BaseScreen_3'>
I am screen: Screen-3
Pushbutton pressed: sel screen. Changing to the screen: <class 'BaseScreen_4'>
I am screen: Screen-4
Button pressed Yes
Your choose was *Yes*, so Changing to the screen: <class 'BaseScreen_1'>
I am screen: Screen-1
https://github.com/peterhinch/micropython-micro-gui/assets/630637/c760edb1-e05a-44e3-a03c-d1fc2711c0a7
GitHub complains that the video file is corrupt.
I am very busy at the moment - I'm not sure when I'll find time to debug all that code.
Good morning @peterhinch :)
GitHub complains that the video file is corrupt.
That's strange, because I watched it directly from github after I posted the thread - I'm using Chromium. Anyway here is a new url for it (shared in the google drive)
I am very busy at the moment - I'm not sure when I'll find time to debug all that code.
All right, take your time! I did a simpler code as possible as prof of concept about the idea, and to not use much your time!
Thank you so much for all your help!
EDIT: is possible, maybe, for now, at least just to answer that Question Item? That will clarify much things about the design in this Proof of Concept about the idea!
The design assumes that screens are stacked. If screen A opens screen B, which opens screen C, the stack is ABC. If screen C is closed, screen B is visible and the stack is AB. When screen B is closed, only screen A exists. Visually each screen overlays the previous one, in the expectation that the top one will be closed revealing the one below.
This is fundamental to the design. If screen C opens another instance of A - carousel mode - you would end up with ABCA which would pave the way to ABCAB - ABCABC and so on. There would indeed be an ever growing number of instances. Supporting that would require a substantial redesign, a task that I don't intend to undertake. Perhaps there is a way where, when in screen C, on closure the underlying screen B also closes. This is way beyond anything I've tried, or anything any other user has requested.
I'm happy to answer queries on design, such as this one. I'm less happy when the answer is in the docs, which I've had cause to point out several times. My least favourite is being presented with pages of code and an invitation to fix it. This would involve setting up hardware and a substantial commitment of time. Time that I don't have.
Hi @peterhinch
This is fundamental to the design. If screen C opens another instance of A - carousel mode - you would end up with ABCA which would pave the way to ABCAB - ABCABC and so on. There would indeed be an ever growing number of instances. Supporting that would require a substantial redesign, a task that I don't intend to undertake. Perhaps there is a way where, when in screen C, on closure the underlying screen B also closes. This is way beyond anything I've tried, or anything any other user has requested.
To solve this problem I used this strategy: I have a main class
that change to all Screens, so each Screen that user click on any pushbutton (prev, sel, next) or Buttons (Yes, No) I set that user option in a variable accessible by that main class
and call the Screen.shutdown()
and the control back to main class
that will change to new Screen that user choose (from that variable). In this way I never will have a stack of Screens, and will never worry by the self.reg_task()
because as say this doc about the Shutdown: "any tasks registered to the base screen are cancelled
" I did some tests and works - using this approach also fixed that problem 2 as well. If you have some consideration about this approach, please, let me know
I'm less happy when the answer is in the docs, which I've had cause to point out several times. My least favourite is being presented with pages of code and an invitation to fix it. This would involve setting up hardware and a substantial commitment of time. Time that I don't have.
I totally agree. Apologize for that. Is my responsibility read the docs, also write the code and test it. Please, ignore my invite to check my code. I will contact you just if the docs do not exists or docs is not well understand by me, or a bug, otherwise you are correct, I need to do my home work. You already help so much about design asking, strategy, suggestion, examples, docs, new features, bug fix, etc!
Well, I have just one problem yet. When I use self.visible = False
on CloseButton
the pushbutton
prev, sel, next
do not works anymore, I mean, after I click in pushbutton next
for example, everything congeals/stop. But I did a detailed debug and find exactly where it is stopping, and I would like your help how can be fixed that.
It stop in the launch
/gui/primitives/__init__.py
:
def launch(func, tup_args):
print('----------- 1')
print(func.__name__)
print(*tup_args)
res = func(*tup_args)
print('----------- 2')
if isinstance(res, type_coro):
res = asyncio.create_task(res)
return res
Test with self.visible = False
:
$ mpremote run screen_main.py
----------- 1
ctrl_move
1
Ps: Look that it stop/congeal in the res = func(*tup_args)
because do not print the '----------- 2'
I see that ctrl_move
will move the pushbutton next. Maybe as the only active widget on the screen is the CloseButton, as it is invisible the pushbutton
next can't go to that invisible widget and happen this bug.
Test with self.visible = True
:
$ mpremote run screen_main.py
----------- 1
ctrl_move
1
----------- 2
----------- 1
next_screen
next screen
----------- 2
Ps: this works ok
Thank you in advance!
Perhaps setting visible=False
was not a good idea. You're going to have to experiment because I have never tried the case where a screen has no visible controls.
To create an invisible control you might like to try the Checkbox
widget. I suggest this because it is extremely simple, in case you need to modify the code in checkbox.py
. If you created a checkbox that was very small, and set its colors to match the screen background, perhaps this will fit the bill.