1
1
mirror of https://github.com/NixOS/mobile-nixos.git synced 2024-12-04 21:18:28 +03:00

boot/lib: Import lvgui ruby implementation

This commit is contained in:
Samuel Dionne-Riel 2021-01-01 17:50:07 -05:00
parent b88d2565ee
commit ffc4a12fa1
14 changed files with 1930 additions and 0 deletions

50
boot/lib/lvgui/args.rb Normal file
View File

@ -0,0 +1,50 @@
# Extremely minimal and naïve arguments parsing.
module Args
@@parsed = nil
def self.get(key, default = nil)
parse[:values][key] or default
end
def self.unused()
parsed[:unused]
end
def self.parse()
return @@parsed if @@parsed
argv = ARGV.dup
unused = []
# Default values.
# `nil` means it expects a parameter.
# `false` means it's a boolean toggle without parameter.
values = {}
while argv.length > 0
arg = argv.shift
if arg.match(/^--help$/)
# Assumes the programmer will add it
begin
print_help
rescue
$stderr.puts "Sorry, no help defined."
end
exit 0
elsif arg.match(/^--/)
key = arg.sub(/^--/, "").gsub("-", "_").to_sym
if argv.length > 0 && !argv[0].match(/^--/)
values[key] = argv.shift
else
values[key] = true
end
else
unused << arg
end
end
@@parsed = {
unused: unused,
values: values,
}
end
end

100
boot/lib/lvgui/fiddlier.rb Normal file
View File

@ -0,0 +1,100 @@
# This module augments Fiddle with useful helpers.
module LVGL
end
module LVGL::Fiddlier
# Given a +type+, returns the value of the global +name+.
def get_global!(type, name)
addr = handler.sym(name)
raise(Fiddle::DLError, "cannot find symbol #{name}") unless addr
s = struct(["#{type} value"])
s.new(addr).value
end
# Given a +type+, sets the global +name+ to +new_value+.
def set_global!(type, name, new_value)
addr = handler.sym(name)
raise(Fiddle::DLError, "cannot find symbol #{name}") unless addr
s = struct(["#{type} value"])
tmp = s.new(addr)
tmp.value = new_value
tmp.value
end
def get_global_struct!(struct, name)
addr = handler.sym(name)
raise(Fiddle::DLError, "cannot find symbol for struct #{name}") unless addr
struct.new(addr)
end
# Using +set_global!+ and +get_global!+, creates accessors for a global
# variable.
def global!(type, name)
method_name = name.to_sym
define_method(method_name) do
get_global!(type, name)
end
module_function method_name
method_name = "#{name}=".to_sym
define_method(method_name) do |value|
set_global!(type, name, value)
end
module_function method_name
end
# TODO: parse `typedef enum {...} name;`
def enum!(name, values, type: "int")
typedef name.to_s, type.to_s
mod = self.const_set(name.to_sym, Module.new)
current_value = 0
values.each do |data|
if data.is_a? Hash
name = data.keys.first.to_sym
current_value = data.values.first
else
name = data.to_sym
end
mod.const_set(name, current_value)
current_value += 1
end
end
# Flattens the given nested struct
def flatten_struct!(fields, prefix: nil)
fields.map do |field|
type = field.first
name = [prefix, field.last].compact.join("_")
if type.is_a? Array then
flatten_struct!(type, prefix: name)
else
[[type.to_sym, name.to_sym]]
end
end.flatten(1)
end
# Parses nested structs into a flattened Fiddle struct.
# XXX: broken because of struct alignment padding / packing
# -> http://www.catb.org/esr/structure-packing/#_padding
def struct!(fields)
flattened = flatten_struct!(fields).map do |field|
type = field.first
name = field.last
"#{type} #{name}"
end
struct(flattened)
end
# Define the given +sym+ as a function.
# It will auto-wrap the method using a closure.
def bound_method!(sym, sig)
sym = sym.to_sym
module_function sym
ctx = self
@func_map[sym.to_s] = bind(sig) do |*args|
ctx.send(sym, *args)
end
end
end

View File

@ -0,0 +1,2 @@
module LVGL
end

437
boot/lib/lvgui/lvgl/ffi.rb Normal file
View File

@ -0,0 +1,437 @@
# FFI bindings to LVGL.
module LVGL::FFI
extend Fiddle::BasicTypes
extend Fiddle::Importer
extend LVGL::Fiddlier
dlload("liblvgui.so")
# Alias all built-in types to their [u]intXX_t variants.
[
:SHORT,
:LONG,
:LONG_LONG,
:CHAR,
:INT,
].each do |type|
[
"",
"unsigned",
].each do |signedness|
sz = Fiddle.const_get("SIZEOF_#{type}".to_sym) * 8
alias_name = "int#{sz}_t"
aliased_type = type.to_s.downcase.gsub("_", " ")
if signedness == "unsigned"
alias_name = "u#{alias_name}"
aliased_type = "#{signedness} #{aliased_type}"
end
typealias(alias_name, aliased_type)
end
end
typealias("bool", "uint8_t")
# lv_conf.h
typealias("lv_coord_t", "int16_t")
typedef "lv_obj_user_data_t", "void *"
# lvgl/src/lv_misc/lv_color.h
typealias("lv_color_t", "uint32_t")
typealias("lv_opa_t", "uint8_t")
# introspection.h
extern "bool lv_introspection_is_simulator()"
extern "bool lv_introspection_is_debug()"
extern "bool lv_introspection_use_assert_style()"
# lvgl/src/lv_misc/lv_task.h
enum!(:LV_TASK_PRIO, [
:OFF,
:LOWEST,
:LOW,
:MID,
:HIGH,
:HIGHEST,
], type: "uint8_t")
typealias("lv_task_prio_t", "LV_TASK_PRIO")
# lvgl/src/lv_themes/lv_theme.h
extern "void lv_theme_set_current(lv_theme_t *)"
extern "lv_theme_t * lv_theme_get_current(void)"
# lvgl/src/lv_themes/lv_theme_night.h
extern "lv_theme_t * lv_theme_night_init(uint16_t, lv_font_t *)"
extern "lv_theme_t * lv_theme_get_night(void)"
# lvgl/src/lv_core/lv_obj.h
enum!(:LV_EVENT, [
:PRESSED, # < The object has been pressed*/
:PRESSING, # < The object is being pressed (called continuously while pressing)*/
:PRESS_LOST, # < User is still pressing but slid cursor/finger off of the object */
:SHORT_CLICKED, # < User pressed object for a short period of time, then released it. Not called if dragged. */
:LONG_PRESSED, # < Object has been pressed for at least `LV_INDEV_LONG_PRESS_TIME`. Not called if dragged.*/
:LONG_PRESSED_REPEAT, # < Called after `LV_INDEV_LONG_PRESS_TIME` in every
# `LV_INDEV_LONG_PRESS_REP_TIME` ms. Not called if dragged.*/
:CLICKED, # < Called on release if not dragged (regardless to long press)*/
:RELEASED, # < Called in every cases when the object has been released*/
:DRAG_BEGIN,
:DRAG_END,
:DRAG_THROW_BEGIN,
:KEY,
:FOCUSED,
:DEFOCUSED,
:VALUE_CHANGED, # < The object's value has changed (i.e. slider moved) */
:INSERT,
:REFRESH,
:APPLY, # < "Ok", "Apply" or similar specific button has clicked*/
:CANCEL, # < "Close", "Cancel" or similar specific button has clicked*/
:DELETE, # < Object is being deleted */
], type: "uint8_t")
typealias("lv_event_t", "LV_EVENT")
typedef "lv_event_cb_t", "void (*lv_event_cb_t)(struct _lv_obj_t *, lv_event_t)"
#typedef uint8_t lv_res_t;
enum!(:LV_RES, [
{ :INV => 0x00 },
{ :OK => 0x01 },
], type: :uint8_t)
typealias("lv_res_t", "LV_RES")
extern "lv_obj_t * lv_obj_create(lv_obj_t *, const lv_obj_t *)"
extern "const lv_style_t * lv_obj_get_style(const lv_obj_t *)"
extern "void lv_obj_set_style(lv_obj_t *, const lv_style_t *)"
extern "lv_coord_t lv_obj_get_width(const lv_obj_t *)"
extern "lv_coord_t lv_obj_get_height(const lv_obj_t *)"
extern "lv_coord_t lv_obj_get_width_fit(const lv_obj_t *)"
extern "lv_coord_t lv_obj_get_height_fit(const lv_obj_t *)"
extern "void lv_obj_set_width(lv_obj_t *, lv_coord_t)"
extern "void lv_obj_set_height(lv_obj_t *, lv_coord_t)"
extern "lv_coord_t lv_obj_get_x(const lv_obj_t *)"
extern "lv_coord_t lv_obj_get_y(const lv_obj_t *)"
extern "lv_obj_user_data_t lv_obj_get_user_data(const lv_obj_t *)"
extern "lv_obj_user_data_t * lv_obj_get_user_data_ptr(const lv_obj_t *)"
extern "void lv_obj_set_user_data(lv_obj_t *, lv_obj_user_data_t)"
extern "void lv_obj_set_event_cb(lv_obj_t *, lv_event_cb_t)"
extern "const void *lv_event_get_data()"
extern "void lv_obj_set_opa_scale(lv_obj_t *, lv_opa_t)"
extern "lv_opa_t lv_obj_get_opa_scale(const lv_obj_t *)"
extern "void lv_obj_set_pos(lv_obj_t *, lv_coord_t, lv_coord_t)"
extern "void lv_obj_set_x(lv_obj_t *, lv_coord_t)"
extern "void lv_obj_set_y(lv_obj_t *, lv_coord_t)"
extern "void lv_obj_set_parent(lv_obj_t *, lv_obj_t *)"
extern "void lv_obj_set_hidden(lv_obj_t *, bool)"
extern "void lv_obj_set_click(lv_obj_t *, bool)"
extern "void lv_obj_set_top(lv_obj_t *, bool)"
extern "void lv_obj_set_opa_scale_enable(lv_obj_t *, bool)"
extern "lv_opa_t lv_obj_get_opa_scale_enable(const lv_obj_t *)"
extern "void lv_obj_clean(lv_obj_t *)"
extern "lv_res_t lv_obj_del(lv_obj_t *)"
extern "void lv_obj_del_async(struct _lv_obj_t *)"
extern "lv_obj_t * lv_obj_get_parent(const lv_obj_t *)"
extern "bool lv_obj_is_children(const lv_obj_t * obj, const lv_obj_t * target)"
extern "lv_obj_t *lv_obj_get_child_back(const lv_obj_t *, const lv_obj_t *)"
def handle_lv_event(obj_p, event)
#userdata = lv_obj_get_user_data(obj_p)
#instance = userdata.to_value
# Pick from our registry, until we can rehydrate the object type with Fiddle.
instance = LVGL::LVObject::REGISTRY[obj_p.to_i]
instance.instance_exec do
if @event_handler_proc
@event_handler_proc.call(event)
end
end
end
bound_method! :handle_lv_event, "void handle_lv_event_(struct _lv_obj_t *, lv_event_t)"
# lvgl/src/lv_objx/lv_btn.h
extern "lv_obj_t * lv_btn_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_btn_set_ink_in_time(lv_obj_t *, uint16_t)"
extern "void lv_btn_set_ink_wait_time(lv_obj_t *, uint16_t)"
extern "void lv_btn_set_ink_out_time(lv_obj_t *, uint16_t)"
# lvgl/src/lv_objx/lv_cont.h
#typedef uint8_t lv_layout_t;
enum!(:LV_LAYOUT, [
{OFF: 0}, #< No layout */
:CENTER, #< Center objects */
:COL_L, #< Column left align*/
:COL_M, #< Column middle align*/
:COL_R, #< Column right align*/
:ROW_T, #< Row top align*/
:ROW_M, #< Row middle align*/
:ROW_B, #< Row bottom align*/
:PRETTY, #< Put as many object as possible in row and begin a new row*/
:GRID, #< Align same-sized object into a grid*/
], type: "uint8_t")
typealias("lv_layout_t", "LV_LAYOUT")
enum!(:LV_FIT, [
:NONE, #< Do not change the size automatically*/
:TIGHT, #< Shrink wrap around the children */
:FLOOD, #< Align the size to the parent's edge*/
:FILL, #< Align the size to the parent's edge first but if there is an object out of it
# then get larger */
], type: "uint8_t")
typealias("lv_fit_t", "LV_FIT")
# typedef uint8_t lv_cont_style_t;
enum!(:LV_CONT_STYLE, [
:MAIN,
])
typealias("lv_cont_style_t", "LV_CONT_STYLE")
extern "lv_obj_t * lv_cont_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_cont_set_layout(lv_obj_t *, lv_layout_t)"
extern "void lv_cont_set_fit4(lv_obj_t *, lv_fit_t, lv_fit_t, lv_fit_t, lv_fit_t)"
extern "void lv_cont_set_fit2(lv_obj_t *, lv_fit_t, lv_fit_t)"
extern "void lv_cont_set_fit(lv_obj_t *, lv_fit_t)"
# lvgl/src/lv_core/lv_disp.h
extern "lv_obj_t *lv_disp_get_scr_act(lv_disp_t *)"
extern "void lv_disp_load_scr(lv_obj_t *)"
extern "lv_disp_t *lv_disp_get_default()"
extern "lv_obj_t *lv_scr_act()"
# lvgl/src/lv_objx/lv_img.h
extern "lv_obj_t * lv_img_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_img_set_src(lv_obj_t *, const void *)"
# lvgl/src/lv_objx/lv_label.h
enum!(:LV_LABEL_LONG, [
:EXPAND, #< Expand the object size to the text size*/
:BREAK, #< Keep the object width, break the too long lines and expand the object
# height*/
:DOT, #< Keep the size and write dots at the end if the text is too long*/
:SROLL, #< Keep the size and roll the text back and forth*/
:SROLL_CIRC, #< Keep the size and roll the text circularly*/
:CROP, #< Keep the size and crop the text out of it*/
], type: "uint8_t")
typealias("lv_label_long_mode_t", "LV_LABEL_LONG")
enum!(:LV_LABEL_ALIGN, [
:LEFT, #< Align text to left */
:CENTER, #< Align text to center */
:RIGHT, #< Align text to right */
:AUTO, #< Use LEFT or RIGHT depending on the direction of the text (LTR/RTL)*/
], type: "uint8_t")
typealias("lv_label_align_t", "LV_LABEL_ALIGN")
enum!(:LV_LABEL_STYLE, [
:MAIN
], type: "uint8_t")
typealias("lv_label_style_t", "LV_LABEL_STYLE")
extern "lv_obj_t * lv_label_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_label_set_text(lv_obj_t *, const char *)"
# extern "void lv_label_set_text_fmt(lv_obj_t * label, const char * fmt, ...)" varargs?
extern "void lv_label_set_long_mode(lv_obj_t *, lv_label_long_mode_t)"
extern "void lv_label_set_align(lv_obj_t *, lv_label_align_t)"
# lvgl/src/lv_objx/lv_page.h
enum!(:LV_ANIM, [
:OFF,
:ON,
])
typealias("lv_anim_enable_t", "LV_ANIM")
enum!(:LV_PAGE_STYLE, [
:BG,
:SCRL,
:SB,
:EDGE_FLASH,
], type: "uint8_t")
typealias("lv_page_style_t", "LV_PAGE_STYLE")
enum!(:LV_SB_MODE, [
{ :OFF => 0x0 },
{ :ON => 0x1 },
{ :DRAG => 0x2 },
{ :AUTO => 0x3 },
{ :HIDE => 0x4 },
{ :UNHIDE => 0x5 },
], type: "uint8_t")
typealias("lv_sb_mode_t", "LV_SB_MODE")
extern "lv_obj_t * lv_page_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_page_clean(lv_obj_t *)"
extern "lv_obj_t * lv_page_get_scrl(const lv_obj_t *)"
extern "void lv_page_set_scrl_layout(lv_obj_t *, lv_layout_t)"
extern "void lv_page_glue_obj(lv_obj_t *, bool)"
extern "void lv_page_set_style(lv_obj_t *, lv_page_style_t, const lv_style_t *)"
extern "void lv_page_focus(lv_obj_t *, const lv_obj_t *, lv_anim_enable_t)"
# lvgl/src/lv_objx/lv_kb.h
enum!(:LV_KB_MODE, [
:TEXT,
:NUM,
:TEXT_UPPER,
], type: "uint8_t")
typealias("lv_kb_mode_t", "LV_KB_MODE")
enum!(:LV_KB_STYLE, [
:BG,
:BTN_REL,
:BTN_PR,
:BTN_TGL_REL,
:BTN_TGL_PR,
:BTN_INA,
])
typealias("lv_kb_style_t", "LV_KB_STYLE")
extern "lv_obj_t * lv_kb_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_kb_set_ta(lv_obj_t *, lv_obj_t *)"
extern "void lv_kb_set_mode(lv_obj_t *, lv_kb_mode_t)"
extern "void lv_kb_set_cursor_manage(lv_obj_t *, bool)"
extern "void lv_kb_set_map(lv_obj_t *, const char * [])" # ??
extern "void lv_kb_set_ctrl_map(lv_obj_t * , const lv_btnm_ctrl_t [])" # ??
extern "void lv_kb_set_style(lv_obj_t *, lv_kb_style_t, const lv_style_t *)"
extern "lv_obj_t * lv_kb_get_ta(const lv_obj_t *)"
extern "lv_kb_mode_t lv_kb_get_mode(const lv_obj_t *)"
extern "bool lv_kb_get_cursor_manage(const lv_obj_t *)"
extern "const char ** lv_kb_get_map_array(const lv_obj_t *)"
extern "const lv_style_t * lv_kb_get_style(const lv_obj_t *, lv_kb_style_t)"
extern "void lv_kb_def_event_cb(lv_obj_t *, lv_event_t)"
# lvgl/src/lv_objx/lv_ta.h
enum!(:LV_CURSOR, [
:NONE,
:LINE,
:BLOCK,
:OUTLINE,
:UNDERLINE,
{ :HIDDEN => 0x08 },
], type: "uint8_t")
typealias("lv_cursor_type_t", "LV_CURSOR")
enum!(:LV_TA_STYLE, [
:BG,
:SB,
:CURSOR,
:EDGE_FLASH,
:PLACEHOLDER,
])
typealias("lv_ta_style_t", "LV_TA_STYLE")
extern "lv_obj_t * lv_ta_create(lv_obj_t *, const lv_obj_t *)"
extern "void lv_ta_add_char(lv_obj_t *, uint32_t)"
extern "void lv_ta_add_text(lv_obj_t *, const char *)"
extern "void lv_ta_del_char(lv_obj_t *)"
extern "void lv_ta_del_char_forward(lv_obj_t *)"
extern "void lv_ta_set_text(lv_obj_t *, const char *)"
extern "void lv_ta_set_placeholder_text(lv_obj_t *, const char *)"
extern "void lv_ta_set_cursor_pos(lv_obj_t *, int16_t)"
extern "void lv_ta_set_cursor_type(lv_obj_t *, lv_cursor_type_t)"
extern "void lv_ta_set_cursor_click_pos(lv_obj_t *, bool)"
extern "void lv_ta_set_pwd_mode(lv_obj_t *, bool)"
extern "void lv_ta_set_one_line(lv_obj_t *, bool)"
extern "void lv_ta_set_text_align(lv_obj_t *, lv_label_align_t)"
extern "void lv_ta_set_accepted_chars(lv_obj_t *, const char *)"
extern "void lv_ta_set_max_length(lv_obj_t *, uint16_t)"
#extern "void lv_ta_set_insert_replace(lv_obj_t *, const char *)"
extern "void lv_ta_set_sb_mode(lv_obj_t *, lv_sb_mode_t)"
extern "void lv_ta_set_scroll_propagation(lv_obj_t *, bool)"
extern "void lv_ta_set_edge_flash(lv_obj_t *, bool)"
extern "void lv_ta_set_style(lv_obj_t *, lv_ta_style_t, const lv_style_t *)"
extern "void lv_ta_set_text_sel(lv_obj_t *, bool)"
extern "void lv_ta_set_pwd_show_time(lv_obj_t *, uint16_t)"
extern "void lv_ta_set_cursor_blink_time(lv_obj_t *, uint16_t)"
extern "const char * lv_ta_get_text(const lv_obj_t *)"
extern "const char * lv_ta_get_placeholder_text(lv_obj_t *)"
extern "lv_obj_t * lv_ta_get_label(const lv_obj_t *)"
extern "uint16_t lv_ta_get_cursor_pos(const lv_obj_t *)"
extern "lv_cursor_type_t lv_ta_get_cursor_type(const lv_obj_t *)"
extern "bool lv_ta_get_cursor_click_pos(lv_obj_t *)"
extern "bool lv_ta_get_pwd_mode(const lv_obj_t *)"
extern "bool lv_ta_get_one_line(const lv_obj_t *)"
extern "const char * lv_ta_get_accepted_chars(lv_obj_t *)"
extern "uint16_t lv_ta_get_max_length(lv_obj_t *)"
extern "lv_sb_mode_t lv_ta_get_sb_mode(const lv_obj_t *)"
extern "bool lv_ta_get_scroll_propagation(lv_obj_t *)"
extern "bool lv_ta_get_edge_flash(lv_obj_t *)"
extern "const lv_style_t * lv_ta_get_style(const lv_obj_t *, lv_ta_style_t)"
extern "bool lv_ta_text_is_selected(const lv_obj_t *)"
extern "bool lv_ta_get_text_sel_en(lv_obj_t *)"
extern "uint16_t lv_ta_get_pwd_show_time(lv_obj_t *)"
extern "uint16_t lv_ta_get_cursor_blink_time(lv_obj_t *)"
extern "void lv_ta_clear_selection(lv_obj_t *)"
extern "void lv_ta_cursor_right(lv_obj_t *)"
extern "void lv_ta_cursor_left(lv_obj_t *)"
extern "void lv_ta_cursor_down(lv_obj_t *)"
extern "void lv_ta_cursor_up(lv_obj_t *)"
# lvgl/src/lv_core/lv_style.h
#typedef uint8_t lv_border_part_t
enum!(:LV_BORDER, [
{ :NONE => 0x00 },
{ :BOTTOM => 0x01 },
{ :TOP => 0x02 },
{ :LEFT => 0x04 },
{ :RIGHT => 0x08 },
{ :FULL => 0x0F },
{ :INTERNAL => 0x10 },
], type: :uint8_t)
typealias("lv_border_part_t", "LV_BORDER")
enum!(:LV_SHADOW, [
:BOTTOM,
:FULL,
], type: :uint8_t)
typealias("lv_shadow_type_t", "LV_SHADOW")
#extern "void lv_style_init(void)"
extern "void lv_style_copy(lv_style_t *, const lv_style_t *)"
# Animations
typealias "lv_anim_value_t", "int16_t"
typedef "lv_anim_exec_xcb_t", "void (*lv_anim_exec_xcb_t)(void *, lv_anim_value_t)"
typedef "lv_anim_path_cb_t", "lv_anim_value_t (*lv_anim_path_cb_t)(const struct _lv_anim_t *)"
extern "void lv_anim_init(lv_anim_t *)"
extern "void lv_anim_set_exec_cb(lv_anim_t *, void *, lv_anim_exec_xcb_t)"
extern "void lv_anim_create(lv_anim_t *)"
extern "void lv_anim_del(lv_anim_t *)"
extern "void lv_anim_clear_repeat(lv_anim_t *)"
extern "void lv_anim_set_repeat(lv_anim_t *, uint16_t)"
extern "void lv_anim_set_playback(lv_anim_t *, uint16_t)"
extern "void lv_anim_set_time(lv_anim_t *, int16_t, int16_t)"
extern "void lv_anim_set_path_cb(lv_anim_t *, lv_anim_path_cb_t)"
extern "void lv_anim_set_values(lv_anim_t *, lv_anim_value_t, lv_anim_value_t)"
# Focus groups
typedef "lv_group_focus_cb_t", "void (*lv_group_focus_cb_t)(struct _lv_group_t *)"
extern "void lv_anim_core_init()"
extern "lv_group_t * lvgui_get_focus_group()"
extern "void lvgui_focus_ring_disable()"
extern "void lv_group_add_obj(lv_group_t *, lv_obj_t *)"
extern "void lv_group_remove_obj(lv_obj_t *)"
extern "void lv_group_remove_all_objs(lv_group_t *)"
extern "void lv_group_focus_obj(lv_obj_t *)"
extern "void lv_group_focus_next(lv_group_t *)"
extern "void lv_group_focus_prev(lv_group_t *)"
extern "void lv_group_focus_freeze(lv_group_t *, bool)"
extern "void lv_group_set_click_focus(lv_group_t *, bool)"
extern "void lv_group_set_wrap(lv_group_t *, bool)"
extern "lv_obj_t *lv_group_get_focused(const lv_group_t *)"
extern "void lv_group_set_focus_cb(lv_group_t *, lv_group_focus_cb_t)"
extern "lv_obj_t * lv_group_get_focused(const lv_group_t *)"
typedef "lv_group_user_data_t", "void *"
extern "lv_group_user_data_t *lv_group_get_user_data(lv_group_t *)"
extern "void lv_group_set_user_data(lv_group_t *, lv_group_user_data_t)"
def handle_lv_focus(group_p)
#userdata = lv_group_get_user_data(group_p)
#instance = userdata.to_value
# Pick from our registry, until we can rehydrate the object type with Fiddle.
instance = LVGL::LVGroup::REGISTRY[group_p.to_i]
instance.instance_exec do
if @focus_handler_proc
@focus_handler_proc.call()
end
end
end
bound_method! :handle_lv_focus, "void handle_lv_focus_(_lv_group_t *)"
end

View File

@ -0,0 +1,97 @@
module LVGL::FFI
extern "lv_style_t * lvgui_allocate_lv_style()"
extern "uint8_t lvgui_get_lv_style__glass(lv_style_t *)"
extern "void lvgui_set_lv_style__glass(lv_style_t *, uint8_t)"
extern "lv_color_t lvgui_get_lv_style__body_main_color(lv_style_t *)"
extern "void lvgui_set_lv_style__body_main_color(lv_style_t *, lv_color_t)"
extern "lv_color_t lvgui_get_lv_style__body_grad_color(lv_style_t *)"
extern "void lvgui_set_lv_style__body_grad_color(lv_style_t *, lv_color_t)"
extern "lv_coord_t lvgui_get_lv_style__body_radius(lv_style_t *)"
extern "void lvgui_set_lv_style__body_radius(lv_style_t *, lv_coord_t)"
extern "lv_opa_t lvgui_get_lv_style__body_opa(lv_style_t *)"
extern "void lvgui_set_lv_style__body_opa(lv_style_t *, lv_opa_t)"
extern "lv_color_t lvgui_get_lv_style__body_border_color(lv_style_t *)"
extern "void lvgui_set_lv_style__body_border_color(lv_style_t *, lv_color_t)"
extern "lv_coord_t lvgui_get_lv_style__body_border_width(lv_style_t *)"
extern "void lvgui_set_lv_style__body_border_width(lv_style_t *, lv_coord_t)"
extern "lv_border_part_t lvgui_get_lv_style__body_border_part(lv_style_t *)"
extern "void lvgui_set_lv_style__body_border_part(lv_style_t *, lv_border_part_t)"
extern "lv_opa_t lvgui_get_lv_style__body_border_opa(lv_style_t *)"
extern "void lvgui_set_lv_style__body_border_opa(lv_style_t *, lv_opa_t)"
extern "lv_color_t lvgui_get_lv_style__body_shadow_color(lv_style_t *)"
extern "void lvgui_set_lv_style__body_shadow_color(lv_style_t *, lv_color_t)"
extern "lv_coord_t lvgui_get_lv_style__body_shadow_width(lv_style_t *)"
extern "void lvgui_set_lv_style__body_shadow_width(lv_style_t *, lv_coord_t)"
extern "lv_shadow_type_t lvgui_get_lv_style__body_shadow_type(lv_style_t *)"
extern "void lvgui_set_lv_style__body_shadow_type(lv_style_t *, lv_shadow_type_t)"
extern "lv_coord_t lvgui_get_lv_style__body_padding_top(lv_style_t *)"
extern "void lvgui_set_lv_style__body_padding_top(lv_style_t *, lv_coord_t)"
extern "lv_coord_t lvgui_get_lv_style__body_padding_bottom(lv_style_t *)"
extern "void lvgui_set_lv_style__body_padding_bottom(lv_style_t *, lv_coord_t)"
extern "lv_coord_t lvgui_get_lv_style__body_padding_left(lv_style_t *)"
extern "void lvgui_set_lv_style__body_padding_left(lv_style_t *, lv_coord_t)"
extern "lv_coord_t lvgui_get_lv_style__body_padding_right(lv_style_t *)"
extern "void lvgui_set_lv_style__body_padding_right(lv_style_t *, lv_coord_t)"
extern "lv_coord_t lvgui_get_lv_style__body_padding_inner(lv_style_t *)"
extern "void lvgui_set_lv_style__body_padding_inner(lv_style_t *, lv_coord_t)"
extern "lv_color_t lvgui_get_lv_style__text_color(lv_style_t *)"
extern "void lvgui_set_lv_style__text_color(lv_style_t *, lv_color_t)"
extern "lv_color_t lvgui_get_lv_style__text_sel_color(lv_style_t *)"
extern "void lvgui_set_lv_style__text_sel_color(lv_style_t *, lv_color_t)"
extern "lv_font_t * lvgui_get_lv_style__text_font(lv_style_t *)"
extern "void lvgui_set_lv_style__text_font(lv_style_t *, lv_font_t *)"
extern "lv_coord_t lvgui_get_lv_style__text_letter_space(lv_style_t *)"
extern "void lvgui_set_lv_style__text_letter_space(lv_style_t *, lv_coord_t)"
extern "lv_coord_t lvgui_get_lv_style__text_line_space(lv_style_t *)"
extern "void lvgui_set_lv_style__text_line_space(lv_style_t *, lv_coord_t)"
extern "lv_opa_t lvgui_get_lv_style__text_opa(lv_style_t *)"
extern "void lvgui_set_lv_style__text_opa(lv_style_t *, lv_opa_t)"
extern "lv_color_t lvgui_get_lv_style__image_color(lv_style_t *)"
extern "void lvgui_set_lv_style__image_color(lv_style_t *, lv_color_t)"
extern "lv_opa_t lvgui_get_lv_style__image_intense(lv_style_t *)"
extern "void lvgui_set_lv_style__image_intense(lv_style_t *, lv_opa_t)"
extern "lv_opa_t lvgui_get_lv_style__image_opa(lv_style_t *)"
extern "void lvgui_set_lv_style__image_opa(lv_style_t *, lv_opa_t)"
extern "lv_color_t lvgui_get_lv_style__line_color(lv_style_t *)"
extern "void lvgui_set_lv_style__line_color(lv_style_t *, lv_color_t)"
extern "lv_coord_t lvgui_get_lv_style__line_width(lv_style_t *)"
extern "void lvgui_set_lv_style__line_width(lv_style_t *, lv_coord_t)"
extern "lv_opa_t lvgui_get_lv_style__line_opa(lv_style_t *)"
extern "void lvgui_set_lv_style__line_opa(lv_style_t *, lv_opa_t)"
extern "uint8_t lvgui_get_lv_style__line_rounded(lv_style_t *)"
extern "void lvgui_set_lv_style__line_rounded(lv_style_t *, uint8_t)"
extern "lv_anim_t * lvgui_allocate_lv_anim()"
end # module LVGL::FFI

View File

@ -0,0 +1,109 @@
# FFI bindings for a couple of our hacks.
module LVGL::FFI
# TODO: Figure out when the following struct (and this typedef) are at the
# end, this segfaults...
typedef "lv_task_cb_t", "void (*lv_task_cb_t)(struct _lv_task_t *)"
LvTask = struct! [ # {{{
[:uint32_t, :period],
[:uint32_t, :last_run],
[:lv_task_cb_t, :task_cb],
["void *", :user_data],
[:uint8_t, :prio],
[:uint8_t, :once],
] # }}}
extern "void hal_init()"
# TODO: begin/rescue DLError and assume failing is we're not in simulator.
if lv_introspection_is_simulator
global!("int", "monitor_width")
global!("int", "monitor_height")
end
extern "void lv_task_handler()"
def handle_lv_task(lv_task_p)
# Unwrap the lv_task struct we received
lv_task = LvTask.new(lv_task_p.to_i)
# This is the userdata that has been given
userdata = lv_task.user_data
# Side-step our inability to rehydrate an mruby Object properly
task = LVGL::Hacks::LVTask::REGISTRY[userdata.to_i]
# Call the task
task.call()
end
bound_method! :handle_lv_task, "void handle_lv_task_(lv_task_t * task)"
extern "lv_task_t * lv_task_create(lv_task_cb_t, uint32_t, lv_task_prio_t, void *)"
end
# FFI bindings for "hacks" for lv_lib_nanosvg
module LVGL::FFI
# lv_lib_nanosvg/lv_nanosvg.h
extern "void lv_nanosvg_init()"
extern "void lv_nanosvg_resize_next_width(int)"
extern "void lv_nanosvg_resize_next_height(int)"
end
module LVGL::Hacks
def self.init()
puts "??1"
LVGL::FFI.hal_init()
puts "??2"
LVGL::FFI.lv_nanosvg_init()
puts "??3"
end
def self.monitor_height=(v)
if LVGL::Introspection.simulator?
LVGL::FFI.monitor_height = v
end
end
def self.monitor_width=(v)
if LVGL::Introspection.simulator?
LVGL::FFI.monitor_width = v
end
end
def self.theme_night(hue)
LVGL::FFI.lv_theme_set_current(
LVGL::FFI.lv_theme_night_init(hue, 0)
)
end
module LVTask
# Temp hack...
# I need to figure out how to use Fiddle's #to_value to rehydrate an mruby
# Object into its proper form.
REGISTRY = {
# userdata pointer int value => instance
}
def self.create_task(period, prio, task)
userdata = Fiddle::Pointer[task]
REGISTRY[userdata.to_i] = task
LVGL::FFI.lv_task_create(
LVGL::FFI["handle_lv_task"],
period,
prio,
userdata
)
end
def self.handle_tasks()
#$stderr.puts "-> handle_tasks"
LVGL::FFI.lv_task_handler()
#$stderr.puts "<- handle_tasks"
end
end
end
module LVGL::Hacks
module LVNanoSVG
def self.resize_next_width(width)
LVGL::FFI.lv_nanosvg_resize_next_width(width)
end
def self.resize_next_height(height)
LVGL::FFI.lv_nanosvg_resize_next_height(height)
end
end
end

View File

@ -0,0 +1,11 @@
module LVGL::Introspection
def self.debug?()
LVGL::FFI.lv_introspection_is_debug != 0
end
def self.simulator?()
LVGL::FFI.lv_introspection_is_simulator != 0
end
def self.use_assert_style?()
LVGL::FFI.lv_introspection_use_assert_style != 0
end
end

579
boot/lib/lvgui/lvgl/lvgl.rb Normal file
View File

@ -0,0 +1,579 @@
module LVGL
[
:ANIM,
:CONT_STYLE,
:CURSOR,
:EVENT,
:FIT,
:KB_MODE,
:KB_STYLE,
:LABEL_ALIGN,
:LABEL_LONG,
:LABEL_STYLE,
:LAYOUT,
:PAGE_STYLE,
:TASK_PRIO,
:TA_STYLE,
].each do |enum_name|
const_set(enum_name, LVGL::FFI.const_get("LV_#{enum_name}".to_sym))
LVGL.const_get(enum_name).module_exec do
def self.from_value(needle)
self.constants.find do |name|
needle == self.const_get(name)
end
end
end
end
def self.ffi_call!(klass, meth, *args, _initiator_class: nil)
_initiator_class ||= klass
unless klass.const_defined?(:LV_TYPE)
raise "Tried to ffi_call!(..., #{meth}) with a #{_initiator_class.name}, which does not define LV_TYPE"
end
ffi_name = "lv_#{klass.const_get(:LV_TYPE)}_#{meth}".to_sym
if LVGL::FFI.respond_to?(ffi_name)
args = args.map do |arg|
case arg
when nil
0
when false
0
when true
1
else
if arg.respond_to? :lv_obj_pointer
arg.lv_obj_pointer
else
arg
end
end
end
return LVGL::FFI.send(ffi_name, *args)
else
if klass.superclass
return ffi_call!(klass.superclass, meth, *args, _initiator_class: _initiator_class)
else
raise "Could not find #{meth} in the class hierarchy."
end
end
end
class LVDisplay
# This is not actually an object type in LVGL proper.
# Get default display
def self.get_default()
LVGL::FFI.lv_disp_get_default()
end
# Gets the current active screen.
def self.get_scr_act()
LVObject.from_pointer(
LVGL::FFI.lv_disp_get_scr_act(get_default())
)
end
end
class LVObject
LV_TYPE = :obj
# Hack...
# I need to figure out how to use Fiddle's #to_value to rehydrate an mruby
# Object into its proper form.
REGISTRY = {
# @self_pointer int value => instance
}
def initialize(parent = nil, pointer: nil)
@event_handler_proc = nil
unless pointer
parent_ptr =
if parent
parent.lv_obj_pointer
else
nil
end
@self_pointer = LVGL.ffi_call!(self.class, :create, parent_ptr, nil)
else
@self_pointer = pointer
end
register_userdata
unless parent or pointer
$stderr.puts("[HACK] Creating #{self.class.name} as screen. (Switching lv_disp_load_scr!)")
LVGL::FFI.lv_disp_load_scr(@self_pointer)
end
end
def lv_obj_pointer()
@self_pointer
end
def self.from_pointer(pointer)
if REGISTRY[pointer.to_i]
REGISTRY[pointer.to_i]
else
self.new(pointer: pointer)
end
end
def get_style()
style = LVGL.ffi_call!(self.class, :get_style, @self_pointer)
LVGL::LVStyle.from_pointer(style)
end
def set_style(style)
# Prevents the object from being collected
@style = style
LVGL.ffi_call!(self.class, :set_style, @self_pointer, style.lv_style_pointer)
end
def glue_obj(value)
value =
if value
1
else
0
end
LVGL::FFI.lv_page_glue_obj(@self_pointer, value)
end
def method_missing(meth, *args)
LVGL.ffi_call!(self.class, meth, @self_pointer, *args)
end
def event_handler=(cb_proc)
# Hook the handler on-the-fly.
unless @event_handler_proc
LVGL.ffi_call!(self.class, :set_event_cb, @self_pointer, LVGL::FFI["handle_lv_event"])
end
@event_handler_proc = cb_proc
end
def register_userdata()
userdata = Fiddle::Pointer[self]
REGISTRY[@self_pointer.to_i] = self
LVGL.ffi_call!(self.class, :set_user_data, @self_pointer, userdata)
end
def get_parent()
ptr = LVGL.ffi_call!(self.class, :get_parent, @self_pointer)
LVObject.from_pointer(ptr)
end
def get_children()
children = []
last_child = nil
loop do
ptr = LVGL.ffi_call!(self.class, :get_child_back, @self_pointer, last_child)
break if ptr.null?
last_child = LVObject.from_pointer(ptr)
children << last_child
end
children
end
end
class LVContainer < LVObject
LV_TYPE = :cont
def set_layout(*args)
LVGL::FFI.lv_cont_set_layout(@self_pointer, *args)
end
def get_style(type)
# type is unused, see lvgl/src/lv_objx/lv_cont.h
super()
end
def set_style(type, style)
# type is unused, see lvgl/src/lv_objx/lv_cont.h
super(style)
end
end
class LVLabel < LVObject
LV_TYPE = :label
def get_style(type)
# type is unused, see lvgl/src/lv_objx/lv_label.h
super()
end
def set_style(type, style)
# type is unused, see lvgl/src/lv_objx/lv_label.h
super(style)
end
def set_text(text)
# The "\0" thing is a bit scary; it seems that *something* related
# to C string and "\0" in either mruby or LVGL, likely mruby, may
# cause issues when using something like `split` to split a bigger
# string.
#
# My assumption is that the ruby string is not \0 completed, and
# given as-is to the C world via ffi.
LVGL.ffi_call!(self.class, :set_text, @self_pointer, text + "\0")
end
end
class LVImage < LVObject
LV_TYPE = :img
end
class LVPage < LVContainer
LV_TYPE = :page
def set_style(type, style)
# Prevents the object from being collected
@style = style
LVGL.ffi_call!(self.class, :set_style, @self_pointer, type, style.lv_style_pointer)
end
def focus(obj, anim)
ptr =
if obj.respond_to?(:lv_obj_pointer)
obj.lv_obj_pointer
else
obj
end
LVGL.ffi_call!(self.class, :focus, @self_pointer, ptr, anim)
end
end
class LVButton < LVContainer
LV_TYPE = :btn
end
class LVTextArea < LVObject
LV_TYPE = :ta
def add_text(text)
# The "\0" thing is a bit scary; it seems that *something* related
# to C string and "\0" in either mruby or LVGL, likely mruby, may
# cause issues when using something like `split` to split a bigger
# string.
#
# My assumption is that the ruby string is not \0 completed, and
# given as-is to the C world via ffi.
LVGL.ffi_call!(self.class, :add_text, @self_pointer, text + "\0")
end
def set_text(text)
# The "\0" thing is a bit scary; it seems that *something* related
# to C string and "\0" in either mruby or LVGL, likely mruby, may
# cause issues when using something like `split` to split a bigger
# string.
#
# My assumption is that the ruby string is not \0 completed, and
# given as-is to the C world via ffi.
LVGL.ffi_call!(self.class, :set_text, @self_pointer, text + "\0")
end
def set_placeholder_text(text)
# The "\0" thing is a bit scary; it seems that *something* related
# to C string and "\0" in either mruby or LVGL, likely mruby, may
# cause issues when using something like `split` to split a bigger
# string.
#
# My assumption is that the ruby string is not \0 completed, and
# given as-is to the C world via ffi.
LVGL.ffi_call!(self.class, :set_placeholder_text, @self_pointer, text + "\0")
end
def set_accepted_chars(text)
# The "\0" thing is a bit scary; it seems that *something* related
# to C string and "\0" in either mruby or LVGL, likely mruby, may
# cause issues when using something like `split` to split a bigger
# string.
#
# My assumption is that the ruby string is not \0 completed, and
# given as-is to the C world via ffi.
LVGL.ffi_call!(self.class, :set_accepted_chars, @self_pointer, text + "\0")
end
def get_style(style_type)
style = LVGL.ffi_call!(self.class, :get_style, @self_pointer, style_type)
LVGL::LVStyle.from_pointer(style)
end
def set_style(style_type, style)
# Prevents the object from being collected
@_style ||= {}
@_style[style_type] = style
LVGL.ffi_call!(self.class, :set_style, @self_pointer, style_type, style.lv_style_pointer)
end
end
class LVKeyboard < LVObject
LV_TYPE = :kb
def get_style(style_type)
style = LVGL.ffi_call!(self.class, :get_style, @self_pointer, style_type)
LVGL::LVStyle.from_pointer(style)
end
def set_style(style_type, style)
# Prevents the object from being collected
@_style ||= {}
@_style[style_type] = style
LVGL.ffi_call!(self.class, :set_style, @self_pointer, style_type, style.lv_style_pointer)
end
end
# Wraps an +lv_style_t+ in a class with some light duty housekeeping.
class LVStyle
# Given a +Fiddle::Pointer+ pointing to an +lv_style_t+, instantiates
# an LVStyle class, wrapping the struct.
def self.from_pointer(pointer)
instance = LVGL::LVStyle.new()
instance.instance_exec do
@self_pointer = pointer
end
instance
end
# Allocates a new +lv_style_t+, and copies the styles using the LVGL
# +lv_style_copy+.
def initialize_copy(orig)
@self_pointer = LVGL::FFI.lvgui_allocate_lv_style()
LVGL::FFI.lv_style_copy(@self_pointer, orig.lv_style_pointer)
end
def lv_style_pointer()
@self_pointer
end
# Proxy all methods to the struct accessors we are wrapping.
# It's dumb, but it works so well!
def method_missing(meth, *args)
meth =
if meth.to_s.match(/=$/)
"lvgui_set_lv_style__#{meth.to_s[0..-2]}".to_sym
else
"lvgui_get_lv_style__#{meth}".to_sym
end
LVGL::FFI.send(meth, @self_pointer, *args)
end
private
def initialize()
end
public
# Initializes global styles
[
"scr",
"transp",
"transp_tight",
"transp_fit",
"plain",
"plain_color",
"pretty",
"pretty_color",
"btn_rel",
"btn_pr",
"btn_tgl_rel",
"btn_tgl_pr",
"btn_ina",
].each do |name|
global_name = "lv_style_#{name}".downcase
const_name = "style_#{name}".upcase.to_sym
wrapped = self.from_pointer(
LVGL::FFI.handler.sym(global_name)
)
const_set(const_name, wrapped)
end
end
class LVGroup
LV_TYPE = :group
REGISTRY = {
# @self_pointer int value => instance
}
def initialize(pointer: nil)
@focus_handler_proc = nil
unless pointer
raise "(FIXME) Creating a focus group is not implemented"
#@self_pointer = LVGL.ffi_call!(self.class, :create)
else
@self_pointer = pointer
end
register_userdata
end
# Given a +Fiddle::Pointer+ pointing to an +lv_group_t+, instantiates
# an LVGroup class, wrapping the struct.
def self.from_pointer(pointer)
if REGISTRY[pointer.to_i]
REGISTRY[pointer.to_i]
else
self.new(pointer: pointer)
end
end
def initialize_copy(orig)
raise "Not implemented"
end
def lv_group_pointer()
@self_pointer
end
def method_missing(meth, *args)
LVGL.ffi_call!(self.class, meth, @self_pointer, *args)
end
def add_obj(obj)
ptr =
if obj.respond_to?(:lv_obj_pointer)
obj.lv_obj_pointer
else
obj
end
LVGL.ffi_call!(self.class, :add_obj, @self_pointer, ptr)
end
def get_focused()
LVObject.from_pointer(
LVGL.ffi_call!(self.class, :get_focused, @self_pointer)
)
end
def focus_handler=(cb_proc)
# Hook the handler on-the-fly.
unless @focus_handler
LVGL.ffi_call!(self.class, :set_focus_cb, @self_pointer, LVGL::FFI["handle_lv_focus"])
end
@focus_handler_proc = cb_proc
end
def register_userdata()
userdata = Fiddle::Pointer[self]
REGISTRY[@self_pointer.to_i] = self
LVGL.ffi_call!(self.class, :set_user_data, @self_pointer, userdata)
end
end
# Wraps an +lv_anim_t+ in a class with some light duty housekeeping.
class LVAnim
LV_TYPE = :anim
# Given a +Fiddle::Pointer+ pointing to an +lv_anim_t+, instantiates
# an LVAnim class, wrapping the struct.
def self.from_pointer(pointer)
instance = LVGL::LVAnim.new()
instance.instance_exec do
@self_pointer = pointer
end
instance
end
def initialize()
@self_pointer = LVGL::FFI.lvgui_allocate_lv_anim()
self.init
end
def lv_anim_pointer()
@self_pointer
end
def set_exec_cb(obj, cb_name)
fn = LVGL::FFI[cb_name.to_s]
raise "No function for #{cb_name} on LVGL::FFI" unless fn
LVGL.ffi_call!(self.class, "set_exec_cb", @self_pointer, obj.lv_obj_pointer, fn)
end
def method_missing(meth, *args)
LVGL.ffi_call!(self.class, meth, @self_pointer, *args)
end
module Path
# Initializes global animation paths
[
"linear",
"step",
"ease_in",
"ease_out",
"ease_in_out",
"overshoot",
"bounce",
].each do |name|
const_set(
name.upcase.to_sym,
LVGL::FFI.handler.sym("lv_anim_path_#{name}".downcase)
)
end
end
end
module Symbols
AUDIO = "\xef\x80\x81" # 61441, 0xF001
VIDEO = "\xef\x80\x88" # 61448, 0xF008
LIST = "\xef\x80\x8b" # 61451, 0xF00B
OK = "\xef\x80\x8c" # 61452, 0xF00C
CLOSE = "\xef\x80\x8d" # 61453, 0xF00D
POWER = "\xef\x80\x91" # 61457, 0xF011
SETTINGS = "\xef\x80\x93" # 61459, 0xF013
HOME = "\xef\x80\x95" # 61461, 0xF015
DOWNLOAD = "\xef\x80\x99" # 61465, 0xF019
DRIVE = "\xef\x80\x9c" # 61468, 0xF01C
REFRESH = "\xef\x80\xa1" # 61473, 0xF021
MUTE = "\xef\x80\xa6" # 61478, 0xF026
VOLUME_MID = "\xef\x80\xa7" # 61479, 0xF027
VOLUME_MAX = "\xef\x80\xa8" # 61480, 0xF028
IMAGE = "\xef\x80\xbe" # 61502, 0xF03E
EDIT = "\xef\x8C\x84" # 62212, 0xF304
PREV = "\xef\x81\x88" # 61512, 0xF048
PLAY = "\xef\x81\x8b" # 61515, 0xF04B
PAUSE = "\xef\x81\x8c" # 61516, 0xF04C
STOP = "\xef\x81\x8d" # 61517, 0xF04D
NEXT = "\xef\x81\x91" # 61521, 0xF051
EJECT = "\xef\x81\x92" # 61522, 0xF052
LEFT = "\xef\x81\x93" # 61523, 0xF053
RIGHT = "\xef\x81\x94" # 61524, 0xF054
PLUS = "\xef\x81\xa7" # 61543, 0xF067
MINUS = "\xef\x81\xa8" # 61544, 0xF068
EYE_OPEN = "\xef\x81\xae" # 61550, 0xF06E
EYE_CLOSE = "\xef\x81\xb0" # 61552, 0xF070
WARNING = "\xef\x81\xb1" # 61553, 0xF071
SHUFFLE = "\xef\x81\xb4" # 61556, 0xF074
UP = "\xef\x81\xb7" # 61559, 0xF077
DOWN = "\xef\x81\xb8" # 61560, 0xF078
LOOP = "\xef\x81\xb9" # 61561, 0xF079
DIRECTORY = "\xef\x81\xbb" # 61563, 0xF07B
UPLOAD = "\xef\x82\x93" # 61587, 0xF093
CALL = "\xef\x82\x95" # 61589, 0xF095
CUT = "\xef\x83\x84" # 61636, 0xF0C4
COPY = "\xef\x83\x85" # 61637, 0xF0C5
SAVE = "\xef\x83\x87" # 61639, 0xF0C7
CHARGE = "\xef\x83\xa7" # 61671, 0xF0E7
PASTE = "\xef\x83\xAA" # 61674, 0xF0EA
BELL = "\xef\x83\xb3" # 61683, 0xF0F3
KEYBOARD = "\xef\x84\x9c" # 61724, 0xF11C
GPS = "\xef\x84\xa4" # 61732, 0xF124
FILE = "\xef\x85\x9b" # 61787, 0xF158
WIFI = "\xef\x87\xab" # 61931, 0xF1EB
BATTERY_FULL = "\xef\x89\x80" # 62016, 0xF240
BATTERY_3 = "\xef\x89\x81" # 62017, 0xF241
BATTERY_2 = "\xef\x89\x82" # 62018, 0xF242
BATTERY_1 = "\xef\x89\x83" # 62019, 0xF243
BATTERY_EMPTY = "\xef\x89\x84" # 62020, 0xF244
USB = "\xef\x8a\x87" # 62087, 0xF287
BLUETOOTH = "\xef\x8a\x93" # 62099, 0xF293
TRASH = "\xef\x8B\xAD" # 62189, 0xF2ED
BACKSPACE = "\xef\x95\x9A" # 62810, 0xF55A
SD_CARD = "\xef\x9F\x82" # 63426, 0xF7C2
NEW_LINE = "\xef\xA2\xA2" # 63650, 0xF8A2
end
end

View File

@ -0,0 +1,2 @@
module LVGUI
end

View File

@ -0,0 +1,73 @@
module LVGUI
# Extend this to make a "window"
class BaseWindow
include ::Singleton
def self.inherited(superclass)
superclass.class_eval do
unless self.class_variable_defined?(:@@_after_initialize_callback)
self.class_variable_set(:@@_after_initialize_callback, [])
end
end
end
def initialize()
super()
# Initializes LVGUI things if required...
LVGUI.init
# Preps a basic display
@screen = Screen.new()
@header = Header.new(@screen)
@toolbar = Toolbar.new(@screen)
@container = Page.new(@screen)
@focus_group = []
# Dummy object used as a "null" focus
LVGL::LVObject.new(@screen).tap do |obj|
add_to_focus_group(obj)
end
reset_focus_group
self.class.class_variable_get(:@@_after_initialize_callback).each do |cb|
instance_eval &cb
end
end
# Adds an object to the focus group list, and add it to the
# current focus group.
def add_to_focus_group(obj)
@focus_group << obj
LVGUI.focus_group.add_obj(obj)
end
# Re-build the focus group from the elements on the window.
def reset_focus_group()
# Clear the focus group
LVGUI.focus_group.remove_all_objs()
LVGUI.focus_group.focus_handler = ->() do
@container.focus(
LVGUI.focus_group.get_focused,
LVGL::ANIM::OFF
)
end
@focus_group.each do |el|
LVGUI.focus_group.add_obj(el)
end
end
# Switch to this window
def present()
LVGL::FFI.lv_disp_load_scr(@screen.lv_obj_pointer)
reset_focus_group
# Allow the window to do some work every time it is switched to.
on_present
end
def on_present()
end
end
end

301
boot/lib/lvgui/lvgui/gui.rb Normal file
View File

@ -0,0 +1,301 @@
module LVGUI
# Refreshing at 120 times per second *really* helps with the drag operations
# responsiveness. At 60 it feels a bit sluggish.
# This likely comes from the naïve implementation that we are not refreshing at
# 60 times per seconds, but rather, refresh and wait 1/60th of a second. This
# makes the refresh rate a tad slower.
# Boosting to 120 doesn't seem to have ill effects. It's simply refreshed more.
REFRESH_RATE = 120
# UI constants
NIXOS_LIGHT_HUE = 205
NIXOS_DARK_HUE = 220
# Sets things up; back box for some ugly hacks.
def self.init()
return if @initialized
@initialized = true
# This is used by the "simulator".
if Args.get(:resolution)
pair = Args.get(:resolution).split("x")
unless pair.length == 2
$stderr.puts "--resolution <width>x<height>"
exit 2
end
LVGL::Hacks.monitor_width = pair.first.to_i
LVGL::Hacks.monitor_height = pair.last.to_i
else
LVGL::Hacks.monitor_width = 720
LVGL::Hacks.monitor_height = 1280
end
# Get exclusive control of the framebuffer
# By design we will not restore the console at exit.
# We are assuming the target does not necessarily have a console attached to
# the framebuffer, so this program has to be enough by itself.
VTConsole.map_console(0)
puts "init"
# XXX : here is the hang
# Prepare LVGL
LVGL::Hacks.init()
puts "derp"
# Start the animation core
LVGL::FFI.lv_anim_core_init()
# And switch to the desired theme
LVGL::Hacks.theme_night(NIXOS_LIGHT_HUE)
end
# Runs the app, black boxes LVGL things.
def self.main_loop()
# Main loop
while true
LVGL::Hacks::LVTask.handle_tasks
sleep(1.0/REFRESH_RATE)
yield if block_given?
end
end
def self.focus_group()
LVGL::LVGroup.from_pointer(
LVGL::FFI.lvgui_get_focus_group
)
end
def self.focus_ring_disable()
LVGL::FFI.lvgui_focus_ring_disable()
end
# Wraps an LVGL widget.
class Widget
def initialize(widget)
@widget = widget
end
def method_missing(*args)
@widget.send(*args)
end
# Needed to make respond_to? work.
def lv_obj_pointer()
@widget.lv_obj_pointer
end
end
class Button < Widget
def initialize(parent)
super(LVGL::LVButton.new(parent))
set_layout(LVGL::LAYOUT::COL_M)
set_ink_in_time(200)
set_ink_wait_time(100)
set_ink_out_time(500)
set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
@label = LVGL::LVLabel.new(self)
end
def set_label(label)
@label.set_text(label)
end
end
# Common pattern for a "back button".
# Handles its presentation, and handles its behaviour.
class BackButton < Button
# +parent+: Parent object
# +location+: An instance on which `present` can be called.
def initialize(parent, location)
@holder = LVGL::LVContainer.new(parent)
@holder.set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
@holder.set_style(LVGL::CONT_STYLE::MAIN, LVGL::LVStyle::STYLE_TRANSP.dup)
style = @holder.get_style(LVGL::CONT_STYLE::MAIN)
style.body_padding_top = 0
style.body_padding_left = 0
style.body_padding_right = 0
style.body_padding_bottom = 0
super(@holder)
@location = location
set_label("#{LVGL::Symbols::LEFT} Back")
set_fit2(LVGL::FIT::NONE, LVGL::FIT::TIGHT)
set_width(@holder.get_width / 2)
set_x(0)
self.event_handler = ->(event) do
case event
when LVGL::EVENT::CLICKED
location.present()
end
end
end
end
# Implements a clock as a wrapped LVLabel.
class Clock < Widget
def initialize(parent)
super(LVGL::LVLabel.new(parent))
set_align(LVGL::LABEL_ALIGN::LEFT)
set_long_mode(LVGL::LABEL_LONG::CROP)
# Update the text once
update_clock
# Then register a task to update regularly.
@task = LVGL::Hacks::LVTask.create_task(250, LVGL::TASK_PRIO::MID, ->() do
update_clock
end)
end
def update_clock()
now = Time.now
set_text([
:hour,
:min,
:sec,
].map{|fn| now.send(fn).to_s.rjust(2, "0") }.join(":"))
end
end
# Implements a battery widget as a wrapped LVLabel.
class Battery < Widget
def initialize(parent)
super(LVGL::LVLabel.new(parent))
set_align(LVGL::LABEL_ALIGN::RIGHT)
set_long_mode(LVGL::LABEL_LONG::CROP)
@battery = HAL::Battery.main_battery
# Update the text once
update_text
# Then register a task to update regularly.
@task = LVGL::Hacks::LVTask.create_task(1000 * 15, LVGL::TASK_PRIO::LOW, ->() do
update_text
end)
end
def update_text()
if @battery
symbol =
if @battery.charging? then
LVGL::Symbols::CHARGE
elsif @battery.percent == "unknown"
""
elsif @battery.percent > 95
LVGL::Symbols::BATTERY_FULL
elsif @battery.percent > 75
LVGL::Symbols::BATTERY_3
elsif @battery.percent > 45
LVGL::Symbols::BATTERY_2
elsif @battery.percent > 10
LVGL::Symbols::BATTERY_1
else
LVGL::Symbols::BATTERY_EMPTY
end
set_text("#{symbol} #{@battery.percent}%")
else
set_text("")
end
end
end
# Empty invisible widget
class Screen < Widget
def initialize()
super(LVGL::LVContainer.new())
set_layout(LVGL::LAYOUT::COL_M)
style = get_style(LVGL::CONT_STYLE::MAIN).dup
set_style(LVGL::CONT_STYLE::MAIN, style)
style.body_padding_top = 0
style.body_padding_left = 0
style.body_padding_right = 0
style.body_padding_bottom = 0
style.body_padding_inner = 0
end
end
# Scrolling page.
class Page < Widget
def initialize(parent)
@parent = parent
# A "holder" widget to work around idiosyncracies of pages.
@holder = LVGL::LVContainer.new(parent)
@holder.set_fit2(LVGL::FIT::FILL, LVGL::FIT::NONE)
@holder.set_style(LVGL::CONT_STYLE::MAIN, LVGL::LVStyle::STYLE_TRANSP.dup)
# The actual widget we interact with
super(LVGL::LVPage.new(@holder))
style = LVGL::LVStyle::STYLE_TRANSP.dup
# Padding to zero in the actual scrolling widget makes the scrollbar visible
style.body_padding_left = 0
style.body_padding_right = 0
set_style(LVGL::PAGE_STYLE::BG, style)
set_style(LVGL::PAGE_STYLE::SCRL, style)
set_fit2(LVGL::FIT::FILL, LVGL::FIT::NONE)
# Make this scroll
set_scrl_layout(LVGL::LAYOUT::COL_M)
refresh
end
# Call this function when the position of the Page is changed.
# Mainly, this would be after filling the toolbar.
def refresh()
# Filling the parent that is at the root of the screen is apparently broken :/.
@holder.set_height(@parent.get_height_fit - @holder.get_y)
set_height(@holder.get_height - get_y)
end
end
# A container, with a new name
class Toolbar < Widget
def initialize(parent)
super(LVGL::LVContainer.new(parent))
set_height(0)
set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
set_style(LVGL::CONT_STYLE::MAIN, LVGL::LVStyle::STYLE_TRANSP.dup)
style = get_style(LVGL::CONT_STYLE::MAIN)
style.body_padding_top = 0
style.body_padding_bottom = 0
end
end
# Widget implementing the whole header
class Header < Widget
def initialize(parent)
super(LVGL::LVContainer.new(parent))
header_style = get_style(LVGL::CONT_STYLE::MAIN).dup
set_style(LVGL::CONT_STYLE::MAIN, header_style)
header_style.glass = 1
header_style.body_radius = 0
header_style.body_opa = 255 * 0.6
set_fit2(LVGL::FIT::FILL, LVGL::FIT::TIGHT)
set_layout(LVGL::LAYOUT::PRETTY)
# Split 50/50
child_width = (
get_width -
header_style.body_padding_left -
header_style.body_padding_right -
header_style.body_padding_inner*2
) / 2
# [00:00 ]
@clock = Clock.new(self)
@clock.set_width(child_width)
# [ 69%]
@battery = Battery.new(self)
@battery.set_width(child_width)
end
end
end

View File

@ -0,0 +1,92 @@
# Some hardware abstraction, specific to a particular use-case,
# but still must be generic enough.
#
# All of this must be implemented with the goal to produce a phone-oriented
# GUI toolkit.
module LVGUI::HAL
# Provide battery information
class Battery
NODE_BASE = "/sys/class/power_supply"
# Guesstimates the main battery for a list of likely candidates
def self.main_battery()
node = %w{
battery
bms
BAT0
*-battery
}.map { |name| Dir.glob(File.join(NODE_BASE, name)).first }
.compact
.first
if node
Battery.new(node)
else
nil
end
end
def initialize(node)
@node = node
end
# google-walleye
# [nixos@nixos:/sys/class/power_supply]$ cat battery/uevent
# POWER_SUPPLY_NAME=battery
# POWER_SUPPLY_INPUT_SUSPEND=0
# POWER_SUPPLY_STATUS=Charging
# POWER_SUPPLY_HEALTH=Good
# POWER_SUPPLY_PRESENT=1
# POWER_SUPPLY_CHARGE_TYPE=Fast
# POWER_SUPPLY_CAPACITY=56
# POWER_SUPPLY_SYSTEM_TEMP_LEVEL=0
# POWER_SUPPLY_CHARGER_TEMP=364
# POWER_SUPPLY_CHARGER_TEMP_MAX=803
# POWER_SUPPLY_INPUT_CURRENT_LIMITED=1
# POWER_SUPPLY_VOLTAGE_NOW=3780507
# POWER_SUPPLY_VOLTAGE_MAX=4400000
# POWER_SUPPLY_VOLTAGE_QNOVO=-22
# POWER_SUPPLY_CURRENT_NOW=183105
# POWER_SUPPLY_CURRENT_QNOVO=-22
# POWER_SUPPLY_CONSTANT_CHARGE_CURRENT_MAX=2700000
# POWER_SUPPLY_TEMP=320
# POWER_SUPPLY_TECHNOLOGY=Li-ion
# POWER_SUPPLY_STEP_CHARGING_ENABLED=0
# POWER_SUPPLY_STEP_CHARGING_STEP=-1
# POWER_SUPPLY_CHARGE_DISABLE=0
# POWER_SUPPLY_CHARGE_DONE=0
# POWER_SUPPLY_PARALLEL_DISABLE=0
# POWER_SUPPLY_SET_SHIP_MODE=0
# POWER_SUPPLY_CHARGE_FULL=2805000
# POWER_SUPPLY_DIE_HEALTH=Cool
# POWER_SUPPLY_RERUN_AICL=0
# POWER_SUPPLY_DP_DM=0
# POWER_SUPPLY_CHARGE_COUNTER=1455951
# POWER_SUPPLY_CYCLE_COUNT=849
def uevent()
File.read(File.join(@node, "uevent")).split("\n").map do |line|
key, value = line.split("=", 2)
[key.downcase.to_sym, value]
end.to_h
end
def name()
uevent[:power_supply_name] || "unknown"
end
def status()
uevent[:power_supply_status] || "unknown"
end
def percent()
if uevent[:power_supply_capacity]
uevent[:power_supply_capacity].to_i
else
"unknown"
end
end
def charging?()
status.downcase == "charging" || uevent[:power_supply_charge_done] == "1"
end
end
end

View File

@ -0,0 +1,53 @@
# Helpers for window types
module LVGUI
# Helper methods to help creating a "button palette" kind of window.
module ButtonPalette
def add_button(label)
Button.new(@container).tap do |btn|
add_to_focus_group(btn)
btn.glue_obj(true)
btn.set_label(label)
btn.event_handler = ->(event) do
case event
when LVGL::EVENT::CLICKED
yield
end
end
end
end
def add_buttons(list)
list.each do |pair|
label, action = pair
add_button(label, &action)
end
end
end
module Window
# Include with +include LVGUI::Window::WithBackButton+ and
# use e.g. +goes_back_to ->() { MainWindow.instance }+
module WithBackButton
def self.included(base)
base.extend ClassMethods
end
# Class methods included by WithBackButton
module ClassMethods
# A lambda (or proc)'s return value will determine which instance
# of an object the button will link to.
#
# This is done through a proc/lambda because otherwise it ends up
# depending on the singleton instance of windows directly.
def goes_back_to(prc)
class_variable_get(:@@_after_initialize_callback) << proc do
btn = LVGUI::BackButton.new(@toolbar, prc.call())
add_to_focus_group(btn)
@container.refresh
end
end
end
end
end
end

View File

@ -0,0 +1,24 @@
module VTConsole
# Allows unmapping the "vtcon" console from the frame buffer
# This will ensure it doesn't trample on our lvgui.
def self.map_console(value)
# BAD HACK!
# This always tries to map/unmap the console from the framebuffer
# even when not using the framebuffer output! (Simulator)
# TODO: better introspection to allow the app to know it is running in a
# simulated environment.
begin
# We don't know which one(s) are running on the frame buffer.
Dir.glob("/sys/class/vtconsole/vtcon*").each do |dir|
# So we check...
if File.read(File.join(dir, "name")).strip.match(/frame buffer/)
# And only write to those.
File.open(File.join(dir, "bind"), "w") do |file|
file.write(value.to_s)
end
end
end
rescue
end
end
end