Like all things, to get good at making GUIs you need practice. Creating a timers is a good place to start. They are simple enough to build, but they still contain a few complex elements to get you thinking.
Below is some code that creates a timer app. Today we will go through the most important parts of the code, and see how it works. Don’t worry about the size of the code, its very repetitive but not terribly complicated.
To get this example to work you will need to add your screen setup code to the beginning of the code below. If you want more information, you can try How to get a LVGL Micropython screen to work.
import lvgl
background = lvgl.obj(lvgl.scr_act())
background.set_size(320,240)
background.set_style_pad_all(0,0)
background.set_layout( lvgl.LAYOUT_FLEX.value )
background.set_flex_flow( lvgl.FLEX_FLOW.COLUMN )
background.set_flex_align( lvgl.FLEX_ALIGN.CENTER, lvgl.FLEX_ALIGN.CENTER, lvgl.FLEX_ALIGN.CENTER )
content = lvgl.obj(background)
content.set_style_pad_all(0,0)
content.set_size(300,150)
inputs = lvgl.obj(background)
inputs.set_style_pad_all(0,0)
inputs.set_size(300,60)
num_l = []
for x in range(60):
num_l.append(str(x))
num_s = '\n'.join(num_l)
min_r = lvgl.roller(content)
min_r.set_size(80,130)
min_r.align(lvgl.ALIGN.LEFT_MID,20,0)
min_r.set_options(num_s,lvgl.roller.MODE.INFINITE)
min_r.set_visible_row_count(4)
min_l = lvgl.label(content)
min_l.set_text(':min')
min_l.align(lvgl.ALIGN.CENTER,-25,0)
sec_r = lvgl.roller(content)
sec_r.set_size(80,130)
sec_r.align(lvgl.ALIGN.RIGHT_MID,-55,0)
sec_r.set_options(num_s,lvgl.roller.MODE.INFINITE)
sec_r.set_visible_row_count(4)
sec_l = lvgl.label(content)
sec_l.set_text(':sec')
sec_l.align(lvgl.ALIGN.RIGHT_MID,-20,0)
start = lvgl.btn(inputs)
start.set_size(280,40)
start.center()
start_l = lvgl.label(start)
start_l.set_text('START')
start_l.center()
time_l = lvgl.label(content)
time_l.add_flag(lvgl.obj.FLAG.HIDDEN)
time_l.center()
reset = lvgl.btn(inputs)
reset.set_size(142,40)
reset.add_flag(lvgl.obj.FLAG.HIDDEN)
reset.align(lvgl.ALIGN.LEFT_MID,4,0)
reset_l = lvgl.label(reset)
reset_l.set_text('RESET')
reset_l.center()
pause = lvgl.btn(inputs)
pause.set_size(142,40)
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
pause.align(lvgl.ALIGN.RIGHT_MID,-4,0)
pause_l = lvgl.label(pause)
pause_l.set_text('PAUSE')
pause_l.center()
resume = lvgl.btn(inputs)
resume.set_size(142,40)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.align(lvgl.ALIGN.RIGHT_MID,-4,0)
resume_l = lvgl.label(resume)
resume_l.set_text('RESUME')
resume_l.center()
minute = 0
second = 0
def update_time(t):
global minute,second
if minute == 0 and second == 0:
timer.pause()
set_mode()
alarm()
else:
if second == 0:
second = 59
minute -= 1
else:
second -= 1
if second < 10:
s = '0'+str(second)
else:
s = str(second)
time_l.set_text(str(minute)+':'+s)
print(minute,':',second)
timer = lvgl.timer_create(update_time,1000,None)
timer.set_repeat_count(-1)
timer.pause()
def set_mode():
start.clear_flag(lvgl.obj.FLAG.HIDDEN)
min_r.clear_flag(lvgl.obj.FLAG.HIDDEN)
min_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
sec_r.clear_flag(lvgl.obj.FLAG.HIDDEN)
sec_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
time_l.add_flag(lvgl.obj.FLAG.HIDDEN)
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
reset.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
def timer_mode():
start.add_flag(lvgl.obj.FLAG.HIDDEN)
min_r.add_flag(lvgl.obj.FLAG.HIDDEN)
min_l.add_flag(lvgl.obj.FLAG.HIDDEN)
sec_r.add_flag(lvgl.obj.FLAG.HIDDEN)
sec_l.add_flag(lvgl.obj.FLAG.HIDDEN)
time_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
pause.clear_flag(lvgl.obj.FLAG.HIDDEN)
reset.clear_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
def t(m,s):
global minute,second
minute = m
second = s
timer.reset()
timer.resume()
def start_handler(data):
m = min_r.get_selected()
s = sec_r.get_selected()
timer_mode()
if s < 10:
s = '0' + str(s)
s = str(s)
time_l.set_text(str(m)+':'+s)
t(m,int(s))
def pause_handler(data):
timer.pause()
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.clear_flag(lvgl.obj.FLAG.HIDDEN)
def resume_handler(data):
timer.resume()
pause.clear_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
def reset_handler(data):
timer.pause()
set_mode()
pause.add_event_cb(pause_handler,lvgl.EVENT.CLICKED,None)
reset.add_event_cb(reset_handler,lvgl.EVENT.CLICKED,None)
resume.add_event_cb(resume_handler,lvgl.EVENT.CLICKED,None)
start.add_event_cb(start_handler,lvgl.EVENT.CLICKED,None)
def alarm():
cover = lvgl.obj(lvgl.scr_act())
cover.set_size(320,240)
cover.set_style_opa(lvgl.OPA._80,0)
text = lvgl.label(cover)
text.set_text('TIMER DONE')
text.center()
def delete(data):
cover.delete()
cover.add_event_cb(delete,lvgl.EVENT.RELEASED,None)
text.add_event_cb(delete,lvgl.EVENT.RELEASED,None)
The Background
For the program to work, we need to create a background object. We set its size to cover the entire screen, and remove all the padding from it. We will also enable the flex layout for it.
background = lvgl.obj(lvgl.scr_act())
background.set_size(320,240)
background.set_style_pad_all(0,0)
background.set_layout( lvgl.LAYOUT_FLEX.value )
background.set_flex_flow( lvgl.FLEX_FLOW.COLUMN )
background.set_flex_align( lvgl.FLEX_ALIGN.CENTER, lvgl.FLEX_ALIGN.CENTER, lvgl.FLEX_ALIGN.CENTER )
Inside of that object, we create two more objects: one is for all of the objects that show the time and one is for all the buttons.
content = lvgl.obj(background)
content.set_style_pad_all(0,0)
content.set_size(300,150)
inputs = lvgl.obj(background)
inputs.set_style_pad_all(0,0)
inputs.set_size(300,60)
Screens
This program has three major parts, the first is the settings screen, the second is the screen that shows how much time is left, and the third is the time over screen.
The Settings Screen
In the content area, we create two rollers with a range from 0-59 and a label next to each roller.
num_l = []
for x in range(60):
num_l.append(str(x))
num_s = '\n'.join(num_l)
min_r = lvgl.roller(content)
min_r.set_size(80,130)
min_r.align(lvgl.ALIGN.LEFT_MID,20,0)
min_r.set_options(num_s,lvgl.roller.MODE.INFINITE)
min_r.set_visible_row_count(4)
min_l = lvgl.label(content)
min_l.set_text(':min')
min_l.align(lvgl.ALIGN.CENTER,-25,0)
sec_r = lvgl.roller(content)
sec_r.set_size(80,130)
sec_r.align(lvgl.ALIGN.RIGHT_MID,-55,0)
sec_r.set_options(num_s,lvgl.roller.MODE.INFINITE)
sec_r.set_visible_row_count(4)
sec_l = lvgl.label(content)
sec_l.set_text(':sec')
sec_l.align(lvgl.ALIGN.RIGHT_MID,-20,0
In the inputs area, we create the start button.
start = lvgl.btn(inputs)
start.set_size(280,40)
start.center()
start_l = lvgl.label(start)
start_l.set_text('START')
start_l.center()
The Timer Screen
This screen is a little harder because we have to make all of the objects invisible by default.
The only thing we will add to the content section is a label to show how much time is left in the timer. We make the label invisible by adding the HIDDEN flag.
time_l = lvgl.label(content)
time_l.add_flag(lvgl.obj.FLAG.HIDDEN)
time_l.center()
In the inputs area we add three buttons, all of them are invisible.
reset = lvgl.btn(inputs)
reset.set_size(142,40)
reset.add_flag(lvgl.obj.FLAG.HIDDEN)
reset.align(lvgl.ALIGN.LEFT_MID,4,0)
reset_l = lvgl.label(reset)
reset_l.set_text('RESET')
reset_l.center()
pause = lvgl.btn(inputs)
pause.set_size(142,40)
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
pause.align(lvgl.ALIGN.RIGHT_MID,-4,0)
pause_l = lvgl.label(pause)
pause_l.set_text('PAUSE')
pause_l.center()
resume = lvgl.btn(inputs)
resume.set_size(142,40)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.align(lvgl.ALIGN.RIGHT_MID,-4,0)
resume_l = lvgl.label(resume)
resume_l.set_text('RESUME')
resume_l.center()
How We Know the Time
Before we move on to the last screen, lets see how the timer works behind the scenes.
First of all there are two variables: minute and second. They hold how many minutes and seconds are left on the timer.
minute = 0
second = 0
The update Function
Next, we create a function to update the time.
def update_time(t):
In that function, we begin by importing the minute and second variables.
global minute,second
Then, we check if the minute and second variables equal zero.
if minute == 0 and second == 0:
If they do, we we call three functions: one to end the timer (timer.pause), one two go back to the start screen (set_mode), and one to turn on the end screen (alarm).
If you have a speaker connected to your board this is where you could signal an alarm.
timer.pause()
set_mode()
alarm()
If they didn’t equal zero, we minus one second from the current time, and then print the new time to the terminal and to the screen.
else:
if second == 0:
second = 59
minute -= 1
else:
second -= 1
if second < 10:
s = '0'+str(second)
else:
s = str(second)
time_l.set_text(str(minute)+':'+s)
print(minute,':',second)
We want that function to be called once every second, so we will use a LVGL timer to do that.
timer = lvgl.timer_create(update_time,1000,None)
We then make the timer repeat forever.
timer.set_repeat_count(-1)
Finally we turn it off for right now.
timer.pause()
The Helper Functions
There are also a few functions used to help control the timer.
The set_mode function makes all the start screen objects visible and hides the rest of the objects.
def set_mode():
start.clear_flag(lvgl.obj.FLAG.HIDDEN)
min_r.clear_flag(lvgl.obj.FLAG.HIDDEN)
min_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
sec_r.clear_flag(lvgl.obj.FLAG.HIDDEN)
sec_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
time_l.add_flag(lvgl.obj.FLAG.HIDDEN)
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
reset.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
The timer_mode function does the opposite of the previous function it, hides the start screen objects and makes the timer objects visible.
def timer_mode():
start.add_flag(lvgl.obj.FLAG.HIDDEN)
min_r.add_flag(lvgl.obj.FLAG.HIDDEN)
min_l.add_flag(lvgl.obj.FLAG.HIDDEN)
sec_r.add_flag(lvgl.obj.FLAG.HIDDEN)
sec_l.add_flag(lvgl.obj.FLAG.HIDDEN)
time_l.clear_flag(lvgl.obj.FLAG.HIDDEN)
pause.clear_flag(lvgl.obj.FLAG.HIDDEN)
reset.clear_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
The T function is used to set the timer. It sets the minute and second variables and then resets and starts the timer.
def t(m,s):
global minute,second
minute = m
second = s
timer.reset()
timer.resume()
The Last Screen and the Handlers
These are the last few functions.
The start_handler is called when the the start button is clicked. It initializes the timer with the values from the rollers.
def start_handler(data):
m = min_r.get_selected()
s = sec_r.get_selected()
timer_mode()
if s < 10:
s = '0' + str(s)
s = str(s)
time_l.set_text(str(m)+':'+s)
t(m,int(s))
The pause_handler is connected to the pause button and obliviously pauses the timer.
def pause_handler(data):
timer.pause()
pause.add_flag(lvgl.obj.FLAG.HIDDEN)
resume.clear_flag(lvgl.obj.FLAG.HIDDEN)
The resume_handler and reset_handler should be self explanatory.
def resume_handler(data):
timer.resume()
pause.clear_flag(lvgl.obj.FLAG.HIDDEN)
resume.add_flag(lvgl.obj.FLAG.HIDDEN)
def reset_handler(data):
timer.pause()
set_mode()
The last step for these handlers is to connect them to the correct objects for example the pause_handler is connected to the pause button.
pause.add_event_cb(pause_handler,lvgl.EVENT.CLICKED,None)
Here is the rest of them.
reset.add_event_cb(reset_handler,lvgl.EVENT.CLICKED,None)
resume.add_event_cb(resume_handler,lvgl.EVENT.CLICKED,None)
start.add_event_cb(start_handler,lvgl.EVENT.CLICKED,None)
There is one last function and then we are done with this program.
The Alarm
This function is called when the timer is finished. Its creates the timer over screen which is simply an object that covers the entire screen with the text “TIMER DONE”. If you click the object it disappears and you can use the program as usual.
def alarm():
cover = lvgl.obj(lvgl.scr_act())
cover.set_size(320,240)
cover.set_style_opa(lvgl.OPA._80,0)
text = lvgl.label(cover)
text.set_text('TIMER DONE')
text.center()
def delete(data):
cover.delete()
cover.add_event_cb(delete,lvgl.EVENT.RELEASED,None)
text.add_event_cb(delete,lvgl.EVENT.RELEASED,None)
What Next?
The hole point of this program is to get practice with LVGL, so once you get it working, experiment around with it. The color and placement of some of the objects could be improved, and the most glaring problem is that the font for the current time is way to small.
Because enabling larger fonts takes a lot of work, I left that out of this program, but if you want to do it, you can find so more information on the process at How to Change Micropython LVGL Fonts.