The demo is running the following code:
@main
struct Main {
static func main() {
//var device: Device = Device()
stdio_init_all()
System_Init()
LCD_Init(D2U_L2R, 1000)
TP_Init(D2U_L2R)
TP_GetAdFac()
var ctr: Int = 5
while ctr != 0 {
print("Delaying for console access...")
sleep_ms(1000)
ctr -= 1
}
init_atomic_ops() // Custom implementation of atomic ops for RP2040 to make stuff work
lcd_lvgl_init() // Initialize display and inform LVGL about it
var myCounter: Int = 0
let label = LVGLLabel("", alignment: .bottomMid)
var button = LVGLButton("Click Me", eventType: .pressed) { event in
if let event = event, event.eventCode == .pressed {
myCounter += 1
label.setText("You clicked the button \(myCounter) times")
}
}
let _ = LVGLButton("Delete", alignment: .leftMid) { event in
if let event = event, event.eventCode == .pressed {
print("Deleting button")
if (button.exists()) {
button.delete()
} else {
label.setText("Button already deleted!")
}
}
}
let _ = LVGLSlider("", alignment: .topMid, yOffset: 50)
let _ = LVGLSwitch(alignment: .rightMid)
while true {
lv_timer_handler()
sleep_ms(20)
}
}
}
Where, lcd_lvgl_init
is defined as
void lcd_lvgl_init(void) {
lv_init();
display = lv_display_create(480, 320);
if (display == NULL) {
printf("Failed to create display\n");
} else {
printf("Created display successfully\n");
}
lv_display_set_buffers(display, buf1, NULL, sizeof(buf1),
LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_flush_cb(display, my_disp_flush);
touch_driver_init();
printf("Touch driver setup done\n");
// Set up the tick interface
lv_tick_set_cb(my_tick_get_cb);
printf("Callback setup done\n");
}
LVGL Stuff
Here are some snippets to show how I am writing the library:
Enums
enum Color: UInt32 {
case blue = 0x003a57
case white = 0xffffff
case red = 0xff0000
case green = 0x00ff00
case black = 0x000000
func toHex() -> UInt32 {
return self.rawValue
}
}
enum LVAlignment: UInt8 {
case `default` = 0
case topLeft = 1
case topMid = 2
case topRight = 3
case bottomLeft = 4
case bottomMid = 5
case bottomRight = 6
case leftMid = 7
case rightMid = 8
case center = 9
case outTopLeft = 10
case outTopMid = 11
case outTopRight = 12
case outBottomLeft = 13
case outBottomMid = 14
case outBottomRight = 15
case outLeftTop = 16
case outLeftMid = 17
case outLeftBottom = 18
case outRightTop = 19
case outRightMid = 20
case outRightBottom = 21
}
enum LVGLDimension {
case width, height
}
enum LVEventCode: Int {
case all = 0
case pressed
case pressing
case pressLost
case shortClicked
case longPressed
case longPressedRepeat
case clicked
case released
case scrollBegin
case scrollThrowBegin
case scrollEnd
case scroll
case gesture
case key
case rotary
case focused
case defocused
case leave
case hitTest
case indevReset
case hoverOver
case hoverLeave
case coverCheck
case refreshExtDrawSize
case drawMainBegin
case drawMain
case drawMainEnd
case drawPostBegin
case drawPost
case drawPostEnd
case drawTaskAdded
case valueChanged
case insert
case refresh
case ready
case cancel
case create
case delete
case childChanged
case childCreated
case childDeleted
case screenUnloadStart
case screenLoadStart
case screenLoaded
case screenUnloaded
case sizeChanged
case styleChanged
case layoutChanged
case getSelfSize
case invalidateArea
case resolutionChanged
case colorFormatChanged
case refreshRequest
case refreshStart
case refreshReady
case renderStart
case renderReady
case flushStart
case flushFinish
case flushWaitStart
case flushWaitFinish
case vsync
case preprocess
case last = 1000
func toLVEventCode() -> lv_event_code_t {
return lv_event_code_t(rawValue: UInt16(self.rawValue))
}
}
extension UnsafeMutablePointer<lv_event_t> {
var eventCode: LVEventCode? {
let rawValue = Int(lv_event_get_code(self).rawValue)
return LVEventCode(rawValue: rawValue)
}
}
Protocols and Widgets
protocol LVGLObjectProtocol {
var pointer: UnsafeMutablePointer<lv_obj_t>? { get set }
func setPosition(x: Int32)
func setPosition(y: Int32)
func setPosition(x: Int32, y: Int32)
func setSize(width: Int32, height: Int32)
func setSize(height: Int32)
func setSize(width: Int32)
func getDimension(dimension: LVGLDimension) -> Int32
func getContentDimension(dimension: LVGLDimension) -> Int32
func getSelfDimension(dimension: LVGLDimension) -> Int32
func setContentSize(width: Int32, height: Int32)
func setContentSize(width: Int32)
func setContentSize(height: Int32)
func align(alignment: LVAlignment)
func align(alignment: LVAlignment, xOffset: Int32, yOffset: Int32)
func center()
func getParent() -> UnsafeMutablePointer<lv_obj_t>?
func setParent(parentPointer: UnsafeMutablePointer<lv_obj_t>)
func setCallback(
eventType: LVEventCode, _ callback: @escaping (UnsafeMutablePointer<lv_event_t>?) -> Void)
func removeCallback()
mutating func delete()
func exists() -> Bool
/*
TODO:
func refreshSize() -> bool // lv_obj_refr_size
func setLayout(layout: UInt32) // lv_obj_set_layout
func isLayoutPositioned() -> bool // lv_obj_is_layout_positioned
func setLayoutAsDirty() // lv_obj_mark_layout_as_dirty
func updateLayout() // lv_obj_update_layout
func align(to: UnsafeMutablePointer<lv_obj_t>? = nil, alignment: LVAlignment, xOffset: Int32, yOffset: Int32) // lv_obj_align_to
func copyCoords(area to: UnsafeMutablePointer<lv_area_t>) // lv_obj_get_coords
// Get Coords lv_obj_get_x, lv_obj_get_x2, lv_obj_get_y, lv_obj_get_y2, lv_obj_get_x_aligned, lv_obj_get_y_aligned
and a few more...
*/
}
extension LVGLObjectProtocol {
func setPosition(x: Int32) {
lv_obj_set_x(pointer, x)
}
func setPosition(y: Int32) {
lv_obj_set_y(pointer, y)
}
func setPosition(x: Int32, y: Int32) {
lv_obj_set_pos(pointer, x, y)
}
func setSize(width: Int32) {
lv_obj_set_width(pointer, width)
}
func setSize(height: Int32) {
lv_obj_set_height(pointer, height)
}
func setSize(width: Int32, height: Int32) {
lv_obj_set_size(pointer, width, height)
}
func getDimension(dimension: LVGLDimension) -> Int32 {
switch dimension {
case .width:
return lv_obj_get_width(pointer)
case .height:
return lv_obj_get_height(pointer)
}
}
func getContentDimension(dimension: LVGLDimension) -> Int32 {
switch dimension {
case .width:
return lv_obj_get_content_width(pointer)
case .height:
return lv_obj_get_content_height(pointer)
}
}
func getSelfDimension(dimension: LVGLDimension) -> Int32 {
switch dimension {
case .width:
return lv_obj_get_self_width(pointer)
case .height:
return lv_obj_get_self_height(pointer)
}
}
func setContentSize(width: Int32, height: Int32) {
lv_obj_set_content_width(pointer, width)
lv_obj_set_content_height(pointer, height)
}
func setContentSize(width: Int32) {
lv_obj_set_content_width(pointer, width)
}
func setContentSize(height: Int32) {
lv_obj_set_content_height(pointer, height)
}
func align(alignment: LVAlignment) {
lv_obj_set_align(pointer, alignment.rawValue)
}
func align(alignment: LVAlignment, xOffset: Int32, yOffset: Int32) {
lv_obj_align(pointer, alignment.rawValue, xOffset, yOffset)
}
func center() {
lv_obj_center(pointer)
}
func getParent() -> UnsafeMutablePointer<lv_obj_t>? {
let parentPointer: UnsafeMutablePointer<lv_obj_t>? = lv_obj_get_parent(pointer)
return parentPointer
}
func setParent(parentPointer: UnsafeMutablePointer<lv_obj_t>) {
lv_obj_set_parent(pointer, parentPointer)
}
func setCallback(
eventType: LVEventCode,
_ callback: @escaping (UnsafeMutablePointer<lv_event_t>?) -> Void
) {
callbackStore[UnsafeMutableRawPointer(pointer!)] = callback
lv_obj_add_event_cb(
pointer,
{ cCallback($0) },
eventType.toLVEventCode(),
nil
)
}
func removeCallback() {
callbackStore.removeValue(forKey: UnsafeMutableRawPointer(pointer!))
}
mutating func delete() {
if pointer == nil {
print("Pointer already exists")
print("This will be a fatal error in the future")
} else {
lv_obj_delete(pointer)
pointer = nil
}
}
func exists() -> Bool {
if pointer == nil {
return false
}
return true
}
}
Some Widgets
Label
// MARK: - Label
protocol LVGLLabelProtocol: LVGLObjectProtocol {
func setText(_ text: String)
}
struct LVGLLabel: LVGLLabelProtocol {
var pointer: UnsafeMutablePointer<lv_obj_t>?
init(_ text: String, alignment: LVAlignment = .center, xOffset: Int32 = 0, yOffset: Int32 = 0) {
guard let label = lv_label_create(lv_screen_active()) else {
fatalError("Failed to create label")
}
self.pointer = label
setText(text)
align(alignment: alignment, xOffset: xOffset, yOffset: yOffset)
}
func setText(_ text: String) {
text.withCString { cString in
lv_label_set_text(pointer, cString)
}
}
}
Button
// MARK: - Button
protocol LVGLButtonProtocol: LVGLObjectProtocol {
}
struct LVGLButton: LVGLButtonProtocol {
public let label: LVGLLabel
var pointer: UnsafeMutablePointer<lv_obj_t>?
init(
_ text: String, alignment: LVAlignment = .center, textColor: Color = .blue,
xSize: Int32 = 120, ySize: Int32 = 50, xOffset: Int32 = 0, yOffset: Int32 = 0,
eventType: LVEventCode = .all,
callback: @escaping (UnsafeMutablePointer<lv_event_t>?) -> Void = { _ in }
) {
guard let button = lv_button_create(lv_screen_active()) else {
fatalError("Failed to create button")
}
self.pointer = button
self.label = LVGLLabel(
text, alignment: alignment, xOffset: xOffset, yOffset: yOffset
)
self.label.setParent(parentPointer: pointer!)
align(alignment: alignment, xOffset: xOffset, yOffset: yOffset)
setSize(width: xSize, height: ySize)
setCallback(eventType: eventType, callback)
}
}
Switch
// MARK: - Switch
protocol LVGLSwitchProtocol: LVGLObjectProtocol {
var checked: Bool { get set}
func isChecked() -> Bool
mutating func setChecked(_ to: Bool)
}
struct LVGLSwitch: LVGLSwitchProtocol {
public var checked: Bool
var pointer: UnsafeMutablePointer<lv_obj_t>?
init(_ checked: Bool = false, alignment: LVAlignment = .center, xOffset: Int32 = 0, yOffset: Int32 = 0) {
guard let mySwitch = lv_switch_create(lv_screen_active()) else {
fatalError("Failed to create switch")
}
self.pointer = mySwitch
self.checked = checked
align(alignment: alignment, xOffset: xOffset, yOffset: yOffset)
setChecked(checked)
}
func isChecked() -> Bool {
return checked
}
mutating func setChecked(_ to: Bool) {
if (to) {
lv_obj_add_state(pointer, 1)
} else {
lv_obj_remove_state(pointer, 1)
}
self.checked = to
}
}
Callbacks
To handle callbacks for buttons (and any other widgets that support this). I am initializing a global callbacks dictionary
var callbackStore: [UnsafeMutableRawPointer: (UnsafeMutablePointer<lv_event_t>?) -> Void] = [:]
func cCallback(_ event: UnsafeMutablePointer<lv_event_t>?) {
guard let event = event else { return }
let target = lv_event_get_target(event)
if let targetPointer = UnsafeMutableRawPointer(target),
let callback = callbackStore[targetPointer]
{
callback(event)
}
}
The only reason I am providing these code snippets instead of an entire repository is because my current project repository is way too tightly coupled with my Pico project. Since I haven't really worked with much embedded stuff, I do need ideas regarding the general design for this package:
- Should I continue with structs, or should I switch to classes?
- How should the LVGL compilation step be managed? Since LVGL requires you provide
lv_conf.h
when we are compiling LVGL - There has to be a better way to handle callbacks instead of maintaining a global dictionary, right?
Ideally I want to be able to use this same package on macOS/Linux as well since all the user needs to provide is a framebuffer and a flush callback.