@ -7,8 +7,7 @@ local display = require("display")
local controls = require ( " controls " )
local controls = require ( " controls " )
local bluetooth = require ( " bluetooth " )
local bluetooth = require ( " bluetooth " )
local database = require ( " database " )
local database = require ( " database " )
local screen = require ( " screen " )
local settings = { }
local function SettingsScreen ( title )
local function SettingsScreen ( title )
local menu = widgets.MenuScreen {
local menu = widgets.MenuScreen {
@ -31,323 +30,331 @@ local function SettingsScreen(title)
return menu
return menu
end
end
function settings . bluetooth ( )
local BluetoothSettings = screen : new {
local menu = SettingsScreen ( " Bluetooth " )
createUi = function ( self )
self.menu = SettingsScreen ( " Bluetooth " )
local enable_container = menu.content : Object {
flex = {
local enable_container = self.menu . content : Object {
flex_direction = " row " ,
flex = {
justify_content = " flex-start " ,
flex_direction = " row " ,
align_items = " content " ,
justify_content = " flex-start " ,
align_content = " flex-start " ,
align_items = " content " ,
} ,
align_content = " flex-start " ,
w = lvgl.PCT ( 100 ) ,
} ,
h = lvgl.SIZE_CONTENT ,
w = lvgl.PCT ( 100 ) ,
pad_bottom = 1 ,
h = lvgl.SIZE_CONTENT ,
}
pad_bottom = 1 ,
enable_container : Label { text = " Enable " , flex_grow = 1 }
}
local enable_sw = enable_container : Switch { }
enable_container : Label { text = " Enable " , flex_grow = 1 }
enable_sw : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
local enable_sw = enable_container : Switch { }
local enabled = enable_sw : enabled ( )
enable_sw : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
bluetooth.enabled : set ( enabled )
local enabled = enable_sw : enabled ( )
end )
bluetooth.enabled : set ( enabled )
menu.content : Label {
text = " Paired Device " ,
pad_bottom = 1 ,
} : add_style ( theme.settings_title )
local paired_container = menu.content : Object {
flex = {
flex_direction = " row " ,
justify_content = " flex-start " ,
align_items = " flex-start " ,
align_content = " flex-start " ,
} ,
w = lvgl.PCT ( 100 ) ,
h = lvgl.SIZE_CONTENT ,
pad_bottom = 2 ,
}
local paired_device = paired_container : Label {
flex_grow = 1 ,
}
local clear_paired = paired_container : Button { }
clear_paired : Label { text = " x " }
clear_paired : onClicked ( function ( )
bluetooth.paired_device : set ( )
end )
menu.content : Label {
text = " Nearby Devices " ,
pad_bottom = 1 ,
} : add_style ( theme.settings_title )
local devices = menu.content : List {
w = lvgl.PCT ( 100 ) ,
h = lvgl.SIZE_CONTENT ,
}
menu.bindings = {
bluetooth.enabled : bind ( function ( en )
if en then
enable_sw : add_state ( lvgl.STATE . CHECKED )
else
enable_sw : clear_state ( lvgl.STATE . CHECKED )
end
end ) ,
bluetooth.paired_device : bind ( function ( device )
if device then
paired_device : set { text = device.name }
clear_paired : clear_flag ( lvgl.FLAG . HIDDEN )
else
paired_device : set { text = " None " }
clear_paired : add_flag ( lvgl.FLAG . HIDDEN )
end
end ) ,
bluetooth.devices : bind ( function ( devs )
devices : clean ( )
for _ , dev in pairs ( devs ) do
devices : add_btn ( nil , dev.name ) : onClicked ( function ( )
bluetooth.paired_device : set ( dev )
end )
end
end )
end )
}
end
function settings . headphones ( )
self.menu . content : Label {
local menu = SettingsScreen ( " Headphones " )
text = " Paired Device " ,
pad_bottom = 1 ,
} : add_style ( theme.settings_title )
local paired_container = self.menu . content : Object {
flex = {
flex_direction = " row " ,
justify_content = " flex-start " ,
align_items = " flex-start " ,
align_content = " flex-start " ,
} ,
w = lvgl.PCT ( 100 ) ,
h = lvgl.SIZE_CONTENT ,
pad_bottom = 2 ,
}
local paired_device = paired_container : Label {
flex_grow = 1 ,
}
local clear_paired = paired_container : Button { }
clear_paired : Label { text = " x " }
clear_paired : onClicked ( function ( )
bluetooth.paired_device : set ( )
end )
menu.content : Label {
self.menu . content : Label {
text = " Maximum volume limit " ,
text = " Nearby Devices " ,
} : add_style ( theme.settings_title )
pad_bottom = 1 ,
} : add_style ( theme.settings_title )
local devices = self.menu . content : List {
w = lvgl.PCT ( 100 ) ,
h = lvgl.SIZE_CONTENT ,
}
self.bindings = {
bluetooth.enabled : bind ( function ( en )
if en then
enable_sw : add_state ( lvgl.STATE . CHECKED )
else
enable_sw : clear_state ( lvgl.STATE . CHECKED )
end
end ) ,
bluetooth.paired_device : bind ( function ( device )
if device then
paired_device : set { text = device.name }
clear_paired : clear_flag ( lvgl.FLAG . HIDDEN )
else
paired_device : set { text = " None " }
clear_paired : add_flag ( lvgl.FLAG . HIDDEN )
end
end ) ,
bluetooth.devices : bind ( function ( devs )
devices : clean ( )
for _ , dev in pairs ( devs ) do
devices : add_btn ( nil , dev.name ) : onClicked ( function ( )
bluetooth.paired_device : set ( dev )
end )
end
end )
}
end
}
local HeadphonesSettings = screen : new {
createUi = function ( self )
self.menu = SettingsScreen ( " Headphones " )
self.menu . content : Label {
text = " Maximum volume limit " ,
} : add_style ( theme.settings_title )
local volume_chooser = self.menu . content : Dropdown {
options = " Line Level (-10 dB) \n CD Level (+6 dB) \n Maximum (+10dB) " ,
selected = 1 ,
}
local limits = { - 10 , 6 , 10 }
volume_chooser : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
-- luavgl dropdown binding uses 0-based indexing :(
local selection = volume_chooser : get ( ' selected ' ) + 1
volume.limit_db : set ( limits [ selection ] )
end )
local volume_chooser = menu.content : Dropdown {
self.menu . content : Label {
options = " Line Level (-10 dB) \n CD Level (+6 dB) \n Maximum (+10dB) " ,
text = " Left/Right balance " ,
selected = 1 ,
} : add_style ( theme.settings_title )
}
local limits = { - 10 , 6 , 10 }
local balance = self.menu . content : Slider {
volume_chooser : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
w = lvgl.PCT ( 100 ) ,
-- luavgl dropdown binding uses 0-based indexing :(
h = 5 ,
local selection = volume_chooser : get ( ' selected ' ) + 1
range = { min = - 100 , max = 100 } ,
volume.limit_db : set ( limits [ selection ] )
value = 0 ,
end )
}
balance : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
menu.content : Label {
volume.left_bias : set ( balance : value ( ) )
text = " Left/Right balance " ,
end )
} : add_style ( theme.settings_title )
local balance = menu.content : Slider {
w = lvgl.PCT ( 100 ) ,
h = 5 ,
range = { min = - 100 , max = 100 } ,
value = 0 ,
}
balance : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
volume.left_bias : set ( balance : value ( ) )
end )
local balance_label = menu.content : Label { }
local balance_label = self.menu . content : Label { }
menu.bindings = {
self.bindings = {
volume.limit_db : bind ( function ( limit )
volume.limit_db : bind ( function ( limit )
for i = 1 , # limits do
for i = 1 , # limits do
if limits [ i ] == limit then
if limits [ i ] == limit then
volume_chooser : set { selected = i - 1 }
volume_chooser : set { selected = i - 1 }
end
end
end
end
end ) ,
end ) ,
volume.left_bias : bind ( function ( bias )
volume.left_bias : bind ( function ( bias )
balance : set {
balance : set {
value = bias
value = bias
}
if bias < 0 then
balance_label : set {
text = string.format ( " Left %.2fdB " , bias / 4 )
}
}
elseif bias > 0 then
if bias < 0 then
balance_label : set {
balance_label : set {
text = string.format ( " Right %.2fdB " , - bias / 4 )
text = string.format ( " Left %.2fdB " , bias / 4 )
}
}
else
elseif bias > 0 then
balance_label : set { text = " Balanced " }
balance_label : set {
end
text = string.format ( " Right %.2fdB " , - bias / 4 )
end ) ,
}
}
else
balance_label : set { text = " Balanced " }
return menu
end
end
end ) ,
}
function settings . display ( )
end
local menu = SettingsScreen ( " Display " )
}
local brightness_title = menu.content : Object {
local DisplaySettings = screen : new {
flex = {
createUi = function ( self )
flex_direction = " row " ,
self.menu = SettingsScreen ( " Display " )
justify_content = " flex-start " ,
align_items = " flex-start " ,
local brightness_title = self.menu . content : Object {
align_content = " flex-start " ,
flex = {
} ,
flex_direction = " row " ,
w = lvgl.PCT ( 100 ) ,
justify_content = " flex-start " ,
h = lvgl.SIZE_CONTENT ,
align_items = " flex-start " ,
}
align_content = " flex-start " ,
brightness_title : Label { text = " Brightness " , flex_grow = 1 }
} ,
local brightness_pct = brightness_title : Label { }
w = lvgl.PCT ( 100 ) ,
brightness_pct : add_style ( theme.settings_title )
h = lvgl.SIZE_CONTENT ,
}
local brightness = menu.content : Slider {
brightness_title : Label { text = " Brightness " , flex_grow = 1 }
w = lvgl.PCT ( 100 ) ,
local brightness_pct = brightness_title : Label { }
h = 5 ,
brightness_pct : add_style ( theme.settings_title )
range = { min = 0 , max = 100 } ,
value = display.brightness : get ( ) ,
local brightness = self.menu . content : Slider {
}
w = lvgl.PCT ( 100 ) ,
brightness : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
h = 5 ,
display.brightness : set ( brightness : value ( ) )
range = { min = 0 , max = 100 } ,
end )
value = display.brightness : get ( ) ,
}
menu.bindings = {
brightness : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
display.brightness : bind ( function ( b )
display.brightness : set ( brightness : value ( ) )
brightness_pct : set { text = tostring ( b ) .. " % " }
end )
end )
}
return menu
self.bindings = {
end
display.brightness : bind ( function ( b )
brightness_pct : set { text = tostring ( b ) .. " % " }
end )
}
end
}
function settings . input ( )
local InputSettings = screen : new {
local menu = SettingsScreen ( " Input Method " )
createUi = function ( self )
self.menu = SettingsScreen ( " Input Method " )
menu.content : Label {
self. menu. content : Label {
text = " Control scheme " ,
text = " Control scheme " ,
} : add_style ( theme.settings_title )
} : add_style ( theme.settings_title )
local schemes = controls.schemes ( )
local schemes = controls.schemes ( )
local option_to_scheme = { }
local option_to_scheme = { }
local scheme_to_option = { }
local scheme_to_option = { }
local option_idx = 0
local option_idx = 0
local options = " "
local options = " "
for i , v in pairs ( schemes ) do
for i , v in pairs ( schemes ) do
option_to_scheme [ option_idx ] = i
option_to_scheme [ option_idx ] = i
scheme_to_option [ i ] = option_idx
scheme_to_option [ i ] = option_idx
if option_idx > 0 then
if option_idx > 0 then
options = options .. " \n "
options = options .. " \n "
end
options = options .. v
option_idx = option_idx + 1
end
end
options = options .. v
option_idx = option_idx + 1
end
local controls_chooser = menu.content : Dropdown {
options = options ,
}
menu.bindings = {
local controls_chooser = self.menu . content : Dropdown {
controls.scheme : bind ( function ( s )
options = options ,
local option = scheme_to_option [ s ]
}
controls_chooser : set ( { selected = option } )
self.bindings = {
controls.scheme : bind ( function ( s )
local option = scheme_to_option [ s ]
controls_chooser : set ( { selected = option } )
end )
}
controls_chooser : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
local option = controls_chooser : get ( ' selected ' )
local scheme = option_to_scheme [ option ]
controls.scheme : set ( scheme )
end )
end )
}
controls_chooser : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
local option = controls_chooser : get ( ' selected ' )
local scheme = option_to_scheme [ option ]
controls.scheme : set ( scheme )
end )
menu.content : Label {
text = " Scroll Sensitivity " ,
} : add_style ( theme.settings_title )
local slider_scale = 4 ; -- Power steering
local sensitivity = menu.content : Slider {
w = lvgl.PCT ( 90 ) ,
h = 5 ,
range = { min = 0 , max = 255 / slider_scale } ,
value = controls.scroll_sensitivity : get ( ) / slider_scale ,
}
sensitivity : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
controls.scroll_sensitivity : set ( sensitivity : value ( ) * slider_scale )
end )
return menu
end
function settings . database ( )
local menu = SettingsScreen ( " Database " )
local db = require ( " database " )
widgets.Row ( menu.content , " Schema version " , db.version ( ) )
widgets.Row ( menu.content , " Size on disk " , string.format ( " %.1f KiB " , db.size ( ) / 1024 ) )
local actions_container = menu.content : Object {
self.menu . content : Label {
w = lvgl.PCT ( 100 ) ,
text = " Scroll Sensitivity " ,
h = lvgl.SIZE_CONTENT ,
} : add_style ( theme.settings_title )
flex = {
flex_direction = " row " ,
local slider_scale = 4 ; -- Power steering
justify_content = " center " ,
local sensitivity = self.menu . content : Slider {
align_items = " space-evenly " ,
w = lvgl.PCT ( 90 ) ,
align_content = " center " ,
h = 5 ,
} ,
range = { min = 0 , max = 255 / slider_scale } ,
pad_top = 4 ,
value = controls.scroll_sensitivity : get ( ) / slider_scale ,
pad_column = 4 ,
}
}
sensitivity : onevent ( lvgl.EVENT . VALUE_CHANGED , function ( )
actions_container : add_style ( theme.list_item )
controls.scroll_sensitivity : set ( sensitivity : value ( ) * slider_scale )
end )
local update = actions_container : Button { }
update : Label { text = " Update " }
update : onClicked ( function ( )
database.update ( )
end )
end
function settings . firmware ( )
local menu = SettingsScreen ( " Firmware " )
local version = require ( " version " )
widgets.Row ( menu.content , " ESP32 " , version.esp ( ) )
widgets.Row ( menu.content , " SAMD21 " , version.samd ( ) )
widgets.Row ( menu.content , " Collator " , version.collator ( ) )
end
function settings . root ( )
local menu = widgets.MenuScreen {
show_back = true ,
title = " Settings " ,
}
menu.list = menu.root : List {
w = lvgl.PCT ( 100 ) ,
h = lvgl.PCT ( 100 ) ,
flex_grow = 1 ,
}
local function section ( name )
menu.list : add_text ( name ) : add_style ( theme.list_heading )
end
end
}
local function submenu ( name , fn )
local item = menu.list : add_btn ( nil , name )
local DatabaseSettings = screen : new {
item : onClicked ( function ( )
createUi = function ( self )
backstack.push ( fn )
self.menu = SettingsScreen ( " Database " )
local db = require ( " database " )
widgets.Row ( self.menu . content , " Schema version " , db.version ( ) )
widgets.Row ( self.menu . content , " Size on disk " , string.format ( " %.1f KiB " , db.size ( ) / 1024 ) )
local actions_container = self.menu . content : Object {
w = lvgl.PCT ( 100 ) ,
h = lvgl.SIZE_CONTENT ,
flex = {
flex_direction = " row " ,
justify_content = " center " ,
align_items = " space-evenly " ,
align_content = " center " ,
} ,
pad_top = 4 ,
pad_column = 4 ,
}
actions_container : add_style ( theme.list_item )
local update = actions_container : Button { }
update : Label { text = " Update " }
update : onClicked ( function ( )
database.update ( )
end )
end )
item : add_style ( theme.list_item )
end
end
}
local FirmwareSettings = screen : new {
createUi = function ( self )
self.menu = SettingsScreen ( " Firmware " )
local version = require ( " version " )
widgets.Row ( self.menu . content , " ESP32 " , version.esp ( ) )
widgets.Row ( self.menu . content , " SAMD21 " , version.samd ( ) )
widgets.Row ( self.menu . content , " Collator " , version.collator ( ) )
end
}
section ( " Audio " )
local LicensesScreen = screen : new {
submenu ( " Bluetooth " , settings.bluetooth )
createUi = function ( self )
submenu ( " Headphones " , settings.headphones )
self.root = require ( " licenses " ) ( )
end
}
return screen : new {
createUi = function ( self )
self.menu = widgets.MenuScreen {
show_back = true ,
title = " Settings " ,
}
self.list = self.menu . root : List {
w = lvgl.PCT ( 100 ) ,
h = lvgl.PCT ( 100 ) ,
flex_grow = 1 ,
}
local function section ( name )
self.list : add_text ( name ) : add_style ( theme.list_heading )
end
section ( " Interface " )
local function submenu ( name , class )
submenu ( " Display " , settings.display )
local item = self.list : add_btn ( nil , name )
submenu ( " Input Method " , settings.input )
item : onClicked ( function ( )
backstack.push ( class : new ( ) )
end )
item : add_style ( theme.list_item )
end
section ( " System " )
section ( " Audio " )
submenu ( " Database " , settings.database )
submenu ( " Bluetooth " , BluetoothSettings )
submenu ( " Firmware " , settings.firmware )
submenu ( " Headphones " , HeadphonesSettings )
submenu ( " Licenses " , function ( )
return require ( " licenses " ) ( )
end )
return menu
section ( " Interface " )
end
submenu ( " Display " , DisplaySettings )
submenu ( " Input Method " , InputSettings )
return settings
section ( " System " )
submenu ( " Database " , DatabaseSettings )
submenu ( " Firmware " , FirmwareSettings )
submenu ( " Licenses " , LicensesScreen )
end
}