This is a simple QML interpreter that hope can be useful for others. It allows use QT creator for build LVGL UIs
import imp, sys
sys.path.append('https://raw.githubusercontent.com/littlevgl/lv_binding_micropython/master/lib')
import display_driver
import lvgl as lv
class Lexer:
def __init__( self, string ):
self.str = string
self.ptr = 0
def skip_spaces( self ):
while( self.is_string( " " ) or self.is_string( "\n" ) ):
pass
def is_name( self ):
self.skip_spaces()
ptr = self.ptr
if( self.is_alpha() ):
while( self.is_alpha() or self.is_digit() or self.is_string( "." ) or self.is_string( "_" ) ):
pass
return True, self.str[ ptr: self.ptr ]
return False, ""
def is_number( self ):
self.skip_spaces()
ptr = self.ptr
if( self.is_digit() ):
while( self.is_digit() or self.is_symbol( "." ) ):
pass
return True, self.str[ ptr: self.ptr ]
return False, ""
def is_qstring( self ):
self.skip_spaces()
ptr = self.ptr
if( self.is_symbol( "\"" ) ):
while( not self.is_symbol( "\"" ) ):
self.ptr += 1
return True, self.str[ ptr: self.ptr ]
return False, ""
def is_symbol( self, symbol ):
self.skip_spaces()
ptr = self.ptr
if( self.str[self.ptr] == symbol ):
self.ptr += 1
return True
return False
def is_digit( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( "0" <= c and c <= "9" ):
self.ptr += 1
return True
return False
def is_alpha( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( "a" <= c and c <= "z" or "A" <= c and c <= "Z" ):
self.ptr += 1
return True
return False
def is_graph( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( c == "\"" ): # to simplify quoted string detection, "\"" is not treated as a graph symbol
return False
if( "!" <= c and c <= "~" ):
self.ptr += 1
return True
return False
def is_string( self, string ):
l = len( string )
if( self.ptr + l - 1 >= len(self.str) ):
return False
if( l == 0 ):
return False
if( string == self.str[self.ptr:self.ptr+l] ):
self.ptr += l
return True
return False
class Parser:
def __init__( self, lexer ):
self.lexer = lexer
def parse( self ):
return self.parse_object()
def parse_object( self ):
name = ""
attribs = []
ptr = self.lexer.ptr
res, name = self.lexer.is_name()
if( res ):
if( self.lexer.is_symbol( "{" ) ):
res, attrib = self.parse_attribute()
if( res ):
attribs += [attrib]
while( True ):#self.lexer.is_symbol( "\n" ) ):
res, attrib = self.parse_attribute()
if( res ):
attribs += [attrib]
else:
break
if( self.lexer.is_symbol( "}" ) ):
return True, (name, attribs)
self.lexer.ptr = ptr
return False, (None, None)
def parse_attribute( self ):
name = ""
attribs = []
ptr = self.lexer.ptr
res, (name, value) = self.parse_property()
if( res ):
return True, (name, value)
res, (name, attribs) = self.parse_object()
if( res ):
return True, (name, attribs)
self.lexer.ptr = ptr
return False, (None, None)
def parse_property( self ):
name = ""
value = ""
ptr = self.lexer.ptr
res, name = self.lexer.is_name()
if( res ):
if( self.lexer.is_symbol( ":" ) ):
res, value = self.lexer.is_name()
if( res ):
return True, (name, value)
res, value = self.lexer.is_number()
if( res ):
value = value.replace("\n", "")
return True, (name, value)
res, value = self.lexer.is_qstring()
if( res ):
return True, (name, value)
self.lexer.ptr = ptr
return False, (None, None)
def dump( self, obj, indent=0 ):
name, attribs = obj
print( " "*indent, name, "{" )
for attrib in attribs:
if( attrib[0][0].isupper() ):
self.dump( attrib, indent+1 )
else:
print( " "*(indent+1), attrib[0], ":", attrib[1] )
print( " "*indent, "}" )
def is_digit( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( "0" <= c and c <= "9" ):
self.ptr += 1
return True
return False
def is_alpha( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( "a" <= c and c <= "z" or "A" <= c and c <= "Z" ):
self.ptr += 1
return True
return False
def is_graph( self ):
if( self.ptr >= len(self.str) ):
return False
c = self.str[self.ptr]
if( c == "\"" ): # to simplify quoted string detection, "\"" is not treated as a graph symbol
return False
if( "!" <= c and c <= "~" ):
self.ptr += 1
return True
return False
def is_string( self, string ):
l = len( string )
if( self.ptr + l - 1 >= len(self.str) ):
return False
if( l == 0 ):
return False
if( string == self.str[self.ptr:self.ptr+l] ):
self.ptr += l
return True
return False
class Qml:
def __init__( self ):
pass
def build( self, obj, parent ):
return self.build_obj( obj, parent )
def set_attrib( self, lv_obj, attrib ):
print( "set_attrib", attrib[0], attrib[1] )
if( attrib[0] == "id" ):
pass
elif( attrib[0] == "x" ):
lv_obj.set_x( int( attrib[1] ) )
elif( attrib[0] == "y" ):
lv_obj.set_y( int( attrib[1] ) )
elif( attrib[0] == "width" ):
lv_obj.set_width( int( attrib[1] ) )
elif( attrib[0] == "height" ):
lv_obj.set_height( int( attrib[1] ) )
elif( attrib[0] == "text" ):
try:
#print("set_text", attrib[1][1:-1] )
lv_obj.set_text( attrib[1][1:-1] )
except Exception as e:
print( "except set_text", e )
try:
#print("set_style_local_value_str", attrib[1][1:-1] )
lv_obj.set_style_local_value_str( lv_obj.PART.MAIN, lv.STATE.DEFAULT, attrib[1][1:-1] )
except Exception as e:
print( "except set_style_local_value_str", e )
elif( attrib[0] == "value" ):
lv_obj.set_value( int( float( attrib[1] ) ), lv.ANIM.OFF )
elif( attrib[0] == "color" ):
pass
else:
print("Warning. set_attrib. unsupported attrib name", attrib[0] )
def set_attibs( self, attribs, lv_obj ):
for attrib in attribs:
if( not attrib[0][0].isupper() ):
self.set_attrib( lv_obj, attrib )
def get_id( self, attribs ):
for attrib in attribs:
if( attrib[0] == "id" ):
return attrib[1]
print( "Warning. id not found" )
return None
def build_childs( self, attribs, lv_obj ):
lv_childs = []
for attrib in attribs:
if( attrib[0][0].isupper() ):
lv_childs += [ self.build_obj( attrib, lv_obj ) ]
return lv_childs
def build_obj( self, obj, parent ):
name, attribs = obj
print( "build_obj", name )
if( name == "Rectangle" ):
lv_obj = lv.obj( parent )
elif( name == "Label" ):
lv_obj = lv.label( parent )
elif( name == "Button" ):
lv_obj = lv.btn( parent )
lv_obj.set_style_local_value_str( lv_obj.PART.MAIN, lv.STATE.DEFAULT, "Button" )
elif( name == "CheckBox" ):
lv_obj = lv.checkbox( parent )
elif( name == "Switch" ):
lv_obj = lv.switch( parent )
elif( name == "Slider" ):
lv_obj = lv.slider( parent )
elif( name == "Bar" ):
lv_obj = lv.bar( parent )
elif( name == "DropDown" ):
lv_obj = lv.dropdown( parent )
elif( name == "Column" ):
lv_obj = lv.cont( parent )
lv_obj.set_layout( lv.LAYOUT.COLUMN_LEFT )
#lv_obj.set_fit( lv.FIT.TIGHT )
elif( name == "Row" ):
lv_obj = lv.cont( parent )
lv_obj.set_layout( lv.LAYOUT.ROW_TOP )
#lv_obj.set_fit( lv.FIT.TIGHT )
elif( name == "Grid" ):
lv_obj = lv.cont( parent )
lv_obj.set_layout( lv.LAYOUT.GRID )
#lv_obj.set_fit( lv.FIT.TIGHT )
else:
print( "Warning. build_obj. unsupported obj name", name )
lv_obj = None
self.set_attibs( attribs, lv_obj )
lv_childs = self.build_childs( attribs, lv_obj )
ide = self.get_id( attribs )
return ide, lv_obj, lv_childs
def find( self, name, tree ):
ide, lv_obj, lv_childs = tree
if( name == ide ):
print( "found", name)
return lv_obj
else:
for lv_child in lv_childs:
child = self.find( name, lv_child )
if( child ):
return child
return None
string = """
Rectangle{
width:420
height:320
Button{
id: button_1
x: 8
y: 8
text: "Button 1"
}
Button {
id: button_2
x: 8
y: 54
text: "Button 2"
}
}
"""
lexer = Lexer( string )
parser = Parser( lexer )
qml = Qml()
res, obj = parser.parse()
print("res", res )
if( res ):
parser.dump( obj )
tree = qml.build( obj, lv.scr_act() )
btn1 = qml.find( "button_1", tree )
btn2 = qml.find( "button_2", tree )
btn1.set_event_cb( lambda obj, event: print( "button 1 clicked" ) if event == lv.EVENT.CLICKED else None )
btn2.set_event_cb( lambda obj, event: print( "button 2 clicked" ) if event == lv.EVENT.CLICKED else None )