#- embedded class for LVGL globals -# #- This class stores all globals used by LVGL and cannot be stored in the solidified module -# #- this limits the globals to a single value '_lvgl' -# class LVGL_glob # all variables are lazily initialized to reduce the memory pressure. Until they are used, they consume zero memory var cb_obj # map between a native C pointer (as int) and the corresponding lv_* berry object, also helps marking the objects as non-gc-able var cb_event_closure # mapping for event closures per LVGL native pointer (int) var event_cb # native callback for lv_event #- below are native callbacks mapped to a closure to a method of this instance -# var null_cb # cb called if type is not supported var widget_ctor_cb var widget_dtor_cb var widget_event_cb var widget_struct_default var widget_struct_by_class #- this is the fallback callback, if the event is unknown or unsupported -# static cb_do_nothing = def() print("LVG: call to unsupported callback") end #- register an lv_* object in the mapping -# def register_obj(obj) if self.cb_obj == nil self.cb_obj = {} end var native_ptr = int(obj._p) self.cb_obj[native_ptr] = obj end def get_object_from_ptr(ptr) if self.cb_obj != nil return self.cb_obj.find(ptr) # raise an exception if something is wrong end end def lvgl_event_dispatch(event_ptr) import introspect var event = lv_event(introspect.toptr(event_ptr)) var target = event.target var f = self.cb_event_closure[target] var obj = self.get_object_from_ptr(target) #print('>> lvgl_event_dispatch', f, obj, event) f(obj, event) end def gen_cb(name, f, obj, ptr) #print('>> gen_cb', name, obj, ptr) # record the object, whatever the callback if name == "lv_event_cb" if self.cb_event_closure == nil self.cb_event_closure = {} end if self.event_cb == nil self.event_cb = tasmota.gen_cb(/ event_ptr -> self.lvgl_event_dispatch(event_ptr)) end # encapsulate 'self' in closure self.register_obj(obj) self.cb_event_closure[ptr] = f return self.event_cb # elif name == "" else if self.null_cb == nil self.null_cb = tasmota.gen_cb(self.cb_do_nothing) end return self.null_cb end end def widget_ctor_impl(cl_ptr, obj_ptr) import introspect var cl = lv_obj_class(cl_ptr) var obj = self.get_object_from_ptr(obj_ptr) if self.cb_obj.find(obj) obj = self.cb_obj[obj] end # print("widget_ctor_impl", cl, obj) if type(obj) == 'instance' && introspect.get(obj, 'widget_constructor') obj.widget_constructor(cl) end end def widget_dtor_impl(cl_ptr, obj_ptr) import introspect var cl = lv_obj_class(cl_ptr) var obj = self.get_object_from_ptr(obj_ptr) # print("widget_dtor_impl", cl, obj) if type(obj) == 'instance' && introspect.get(obj, 'widget_destructor') obj.widget_destructor(cl) end end def widget_event_impl(cl_ptr, e_ptr) import introspect var cl = lv_obj_class(cl_ptr) var event = lv_event(e_ptr) var obj_ptr = event.target var obj = self.get_object_from_ptr(int(obj_ptr)) if type(obj) == 'instance' && introspect.get(obj, 'widget_event') obj.widget_event(cl, event) end # print("widget_event_impl", cl, obj_ptr, obj, event) end def widget_cb() if self.widget_ctor_cb == nil self.widget_ctor_cb = tasmota.gen_cb(/ cl, obj -> self.widget_ctor_impl(cl, obj)) end if self.widget_dtor_cb == nil self.widget_dtor_cb = tasmota.gen_cb(/ cl, obj -> self.widget_dtor_impl(cl, obj)) end if self.widget_event_cb == nil self.widget_event_cb = tasmota.gen_cb(/ cl, e -> self.widget_event_impl(cl, e)) end if self.widget_struct_default == nil self.widget_struct_default = lv_obj_class(lv_obj._class).copy() self.widget_struct_default.base_class = lv_obj._class # by default, inherit from base class `lv_obj`, this can be overriden self.widget_struct_default.constructor_cb = self.widget_ctor_cb # set the berry cb dispatchers self.widget_struct_default.destructor_cb = self.widget_dtor_cb self.widget_struct_default.event_cb = self.widget_event_cb end end #- deregister_obj all information linked to a specific LVGL native object (int) -# def deregister_obj(obj) if self.cb_obj != nil self.cb_obj.remove(obj) end if self.cb_event_closure != nil self.cb_event_closure.remove(obj) end end #- initialize a custom widget -# #- arg must be a subclass of lv_obj -# def create_custom_widget(obj, parent) import introspect if !isinstance(obj, lv_obj) raise "value_error", "arg must be a subclass of lv_obj" end if self.widget_struct_by_class == nil self.widget_struct_by_class = {} end var obj_classname = classname(obj) var obj_class_struct = self.widget_struct_by_class.find(obj_classname) # print("classname=",obj_classname,"_class",super(obj)._class) #- not already built, create a new one for this class -# if obj_class_struct == nil self.widget_cb() # set up all structures obj_class_struct = self.widget_struct_default.copy() # get a copy of the structure with pre-defined callbacks obj_class_struct.base_class = super(obj)._class if introspect.get(obj, 'widget_width_def') obj_class_struct.width_def = obj.widget_width_def end if introspect.get(obj, 'widget_height_def') obj_class_struct.height_def = obj.widget_height_def end if introspect.get(obj, 'widget_editable') obj_class_struct.editable = obj.widget_editable end if introspect.get(obj, 'widget_group_def') obj_class_struct.group_def = obj.widget_group_def end if introspect.get(obj, 'widget_instance_size') obj_class_struct.instance_size = obj.widget_instance_size end #- keep a copy of the structure to avoid GC and reuse if needed -# self.widget_struct_by_class[obj_classname] = obj_class_struct end var lv_obj_ptr = lv.obj_class_create_obj(obj_class_struct, parent) obj._p = lv_obj_ptr._p self.register_obj(obj) obj.class_init_obj() end end _lvgl = LVGL_glob() # class lv_custom_widget : lv_obj # # static widget_width_def # # static widget_height_def # # static widget_editable # # static widget_group_def # # static widget_instance_size # # # var percentage # value to display, range 0..100 # var p1, p2, area, line_dsc # instances of objects kept to avoid re-instanciating at each call # def init(parent) # _lvgl.create_custom_widget(self, parent) # # own values # self.percentage = 100 # # pre-allocate buffers # self.p1 = lv_point() # self.p2 = lv_point() # self.area = lv_area() # self.line_dsc = lv_draw_line_dsc() # end # # def widget_constructor(cl) # # print("widget_constructor", cl) # # end # # def widget_destructor(cl) # # print("widget_destructor", cl) # # end # def widget_event(cl, event) # var res = lv.obj_event_base(cl, event) # if res != lv.RES_OK return end # def atleast1(x) if x >= 1 return x else return 1 end end # # the model is that we have 4 bars and inter-bar (1/4 of width) # var height = self.get_height() # var width = self.get_width() # var inter_bar = atleast1(width / 15) # var bar = atleast1((width - inter_bar * 3) / 4) # var bar_offset = bar / 2 # var code = event.code # if code == lv.EVENT_DRAW_MAIN # var clip_area = lv_area(event.param) # print("widget_event DRAW", clip_area.tomap()) # # lv.event_send(self, lv.EVENT_DRAW_MAIN, clip_area) # # get coordinates of object # self.get_coords(self.area) # var x_ofs = self.area.x1 # var y_ofs = self.area.y1 # lv.draw_line_dsc_init(self.line_dsc) # initialize lv_draw_line_dsc structure # self.init_draw_line_dsc(lv.PART_MAIN, self.line_dsc) # self.line_dsc.round_start = 1 # self.line_dsc.round_end = 1 # self.line_dsc.width = bar # var on_color = self.get_style_line_color(lv.PART_MAIN | lv.STATE_DEFAULT) # var off_color = self.get_style_bg_color(lv.PART_MAIN | lv.STATE_DEFAULT) # lv.event_send(self, lv.EVENT_DRAW_PART_BEGIN, self.line_dsc) # for i:0..3 # 4 bars # self.line_dsc.color = self.percentage >= (i+1)*20 ? on_color : off_color # self.p1.y = y_ofs + height - 1 - bar_offset # self.p1.x = x_ofs + i * (bar + inter_bar) + bar_offset # self.p2.y = y_ofs + ((3 - i) * (height - bar)) / 4 + bar_offset # self.p2.x = self.p1.x # lv.draw_line(self.p1, self.p2, clip_area, self.line_dsc) # end # lv.event_send(self, lv.EVENT_DRAW_PART_END, self.line_dsc) # end # end # def set_percentage(v) # var old_bars = self.percentage / 5 # if v > 100 v = 100 end # if v < 0 v = 0 end # self.percentage = v # if old_bars != v / 5 # self.invalidate() # be frugal and avoid updating the widget if it's not needed # end # end # def get_percentage() # return self.percentage # end # end # ########## ########## ########## ########## ########## ########## ########## ########## # lv.start() # hres = lv.get_hor_res() # should be 320 # vres = lv.get_ver_res() # should be 240 # scr = lv.scr_act() # default screean object # f20 = lv.montserrat_font(20) # load embedded Montserrat 20 # scr.set_style_bg_color(lv_color(0x0000A0), lv.PART_MAIN | lv.STATE_DEFAULT) # w = lv_custom_widget(scr)