Once you have created a project the normal way with buttons, sliders, and such, you sometimes would like to do a little more. Maybe you want to fade out a portion of your screen or darken parts of it. For that reason LVGL has masks.
Masks allow you to disable a portion of the screen from being edited. If you created a button, any part of it that fell inside a mask would not be drawn, making that part invisible.
Today, I will show you how to use the line mask. We will use it twice. First to darken the left side of the screen and second to darken the right side of the screen. The center is left untouched.
Cautions
Masks are LVGL’s lowest drawing functions which means that they can be very glitchy. If they break, they can make your GUI look like it went through a blender. Thankfully they don’t cause any real damage. They are just hard to work with.
The Screen
To get this example to work you will need to add your screen setup code to the beginning of the example. If you want more information, you can try How to get a LVGL Micropython screen to work.
Here the code is
import lvgl
def draw_rect(x1,y1,x2,y2,ctx,opa):
rect = lvgl.draw_rect_dsc_t()
rect.init()
rect.bg_opa = opa
rect.bg_color = lvgl.color_hex(0x000000)
size_and_spot = lvgl.area_t()
size_and_spot.x1 = x1
size_and_spot.y1 = y1
size_and_spot.x2 = x2
size_and_spot.y2 = y2
ctx.rect(rect,size_and_spot)
def mask(data):
line = lvgl.draw_mask_line_param_t()
line.points_init(35,240,65,0,0)
num = lvgl.draw_mask_add(line,None)
draw_rect(0,0,320,240,data.get_draw_ctx(),lvgl.OPA._50)
lvgl.draw_mask_remove_id(num)
line2 = lvgl.draw_mask_line_param_t()
line2.points_init(255,240,285,0,1)
num2 = lvgl.draw_mask_add(line2,None)
draw_rect(0,0,320,240,data.get_draw_ctx(),lvgl.OPA._50)
lvgl.draw_mask_remove_id(num2)
scr = lvgl.scr_act()
scr.add_event_cb(mask,lvgl.EVENT.DRAW_POST_END,None)
#This is just some code to create something on the screen so we can show the mask is working
textarea = lvgl.textarea( lvgl.scr_act() )
textarea.set_text( 'Hello this is a test to show how masks work. The left and right sides of the screen should be darkened.' )
textarea.set_size(250,200)
textarea.center()
#End of random code
scr.invalidate()
How It Works
Masks are complicated so to make this example more complete, below is an explanation on how the code works and some of the options that are available to masks.
The Setup
To use LVGL we need to import it. This would also be a good spot to add your screen setup code.
import lvgl
Drawing Rectangles
To prove that our mask is working, we need to have a function that edits part of the screen. Conventionally, masks are pared with draw descriptors to do this.
Draw descriptors are also complicated so I will not explain them here. You can learn more about them at How to Draw a Rectangle with Micropython LVGL if you wish.
The function below works by selecting an area of the screen and darken it by the amount you set.
def draw_rect(x1,y1,x2,y2,ctx,opa):
rect = lvgl.draw_rect_dsc_t()
rect.init()
rect.bg_opa = opa
rect.bg_color = lvgl.color_hex(0x000000)
size_and_spot = lvgl.area_t()
size_and_spot.x1 = x1
size_and_spot.y1 = y1
size_and_spot.x2 = x2
size_and_spot.y2 = y2
ctx.rect(rect,size_and_spot)
The Handlar
You have to think about what time you use masks. If you use them at the wrong time half of your screen might disappear.
So we use handlers to get all the timing right. To start we need to create a new function.
def mask(data):
Then we are going to create a new mask parameter. These store all our mask’s settings.
line = lvgl.draw_mask_line_param_t()
Then we need to initialize all the settings. The prototype of that function looks like this points_init(x1,y1,x2,y2,side). This function is used to create a line.
The starting coordinates of the line are x1 and y1 and the end coordinates are x2 and y2. The side variable uses numbers to control which side of the line to mask off and which side to keep.
The Side numbers are show below.
SIDE TO KEEP | Left | Right | Top | Bottom |
NUMBER | 0 | 1 | 2 | 3 |
line.points_init(35,240,65,0,0)
Now that we have created the line mask. We need to apply it so it actually masks off part of the screen. The function that does this also gives us a number we need to store for later.
num = lvgl.draw_mask_add(line,None)
With the mask finally in place, we are going to use the draw function we defined earlier to darken the entire screen. Of course the mask will actually only let it darken part of the screen.
draw_rect(0,0,320,240,data.get_draw_ctx(),lvgl.OPA._50)
That was all we need the mask for, so now we need to remove it. When we applied the mask we got a number, that we stored in the variable num. With that number, we can delete the mask.
lvgl.draw_mask_remove_id(num)
Now to finish this program we need to do all that again.
We begin by creating a new line mask.
line2 = lvgl.draw_mask_line_param_t()
Next we initialize its points.
line2.points_init(255,240,285,0,1)
After that we apply it to the screen.
num2 = lvgl.draw_mask_add(line2,None)
Then we darken the screen while the mask is applied.
draw_rect(0,0,320,240,data.get_draw_ctx(),lvgl.OPA._50)
Finally, we delete the second mask now that its not needed.
lvgl.draw_mask_remove_id(num2)
Setting the Handler Up
We just spent a lot of work creating that handler, but unless we add it to an object it will not run. For this project we will add it to the entire screen but masks can be added to normal LVGL objects to.
First we need to get the the screen object.
scr = lvgl.scr_act()
Then we add the handler to the screen.
scr.add_event_cb(mask,lvgl.EVENT.DRAW_POST_END,None)
You may have noticed the lvgl.EVENT.DRAW_POST_END. There are four main events that you can use here. They control at what stage of drawing your masks are created and deleted.
lvgl.EVENT. | Time |
---|---|
DRAW_MAIN_BEGIN | Before any thing is drawn |
DRAW_MAIN_END | After most things are drawn |
DRAW_POST_BEGIN | Before Post drawing (aka. mostly scrollbars) |
DRAW_POST_END | After everything is drawn |
Besides just creating and deleting a mask in one event, it is also possible to create a mask and delete it in two different events for example you could create a mask in MAIN_BEGIN and then delete it in POST_END.
The Content
To show that the masks are working we need content. Below is some code that creates a textarea and gives it some text.
#This is just some code to create something on the screen so we can show the mask is working
textarea = lvgl.textarea( lvgl.scr_act() )
textarea.set_text( 'Hello this is a test to show how masks work. The left and right sides of the screen should be darkened.' )
textarea.set_size(250,200)
textarea.center()
#End of random code
INVALIDATE
This is a very important step not to forget. Because we created the masks, LVGL doesn’t know they exist and so it has not updated the screen. Calling invalidate on the screen will force it to update and show that our code is working.
scr.invalidate()
What Next?
You have seen the code above. If you try running it, a textarea should appear then the left and right side of the screen should darken. Once you get it to work, experimenting with moving the masks around the screen is a great way to increase your understanding of masks.
The main reason we use masks is because they are a fast way to draw complex shapes. Once you understand the line mask, its a great idea to learn the other masks. They are the angle, fade, map, radius, and polygon masks.
If you are looking for information on them the LVGL source code is where I found most of the information about them and the LVGL docs give a basic description of how they work.