diff --git a/README.md b/README.md index 761fb318..4dd21c2e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ This repo contains the SD Card contents for all supported EdgeTX Radios. +### Preparing your SD Card + +Tools like [Buddy](https://buddy.edgetx.org) will do most of this automatically for you with a couple of clicks. + +However, if you want to do this manually: +1. Download the appropriate zip file for your radio (either listed below, or in [sdcard.json](https://github.com/EdgeTX/edgetx-sdcard/blob/master/sdcard.json)) from the [releases page](https://github.com/EdgeTX/edgetx-sdcard/releases) (expand the Assets heading if you don't see the files). +2. Unzip the zip archive and put the contents onto a FAT32-formatted (NOT exFAT!) SD Card (preferably smaller than 32GB). +3. If you want the voice pack also, download your preferred voicepack language from the [Voice Pack Repo](https://github.com/EdgeTX/edgetx-sdcard-sounds/releases), unzip the archive, and add it to your SD card. + ### Platforms The contents of this repository are organised by color type and screen size, with zip archives generated for each color type and screen size. @@ -15,6 +24,7 @@ The contents of this repository are organised by color type and screen size, wit - **c480x320.zip** (480x320 pixel landscape orientation color screen radios) - Flysky PL18 - Flysky Paladin EV (PL18EV) + - Jumper T15 - **c320x480.zip** (320x480 pixel portrait orientation color screen radios) - Flysky Nirvana NV14 - Flysky Elysium EL18 @@ -36,9 +46,3 @@ The contents of this repository are organised by color type and screen size, wit - FrSky Taranis X9D - FrSky Taranis X9D+ - FrSky Taranis X9D+ 2019 - -### Preparing your SD Card - -First, download the SD Card zip for your radio (listed above) from the [releases page](https://github.com/EdgeTX/edgetx-sdcard/releases). Then unzip the archive and put the contents onto a FAT32-formatted SD Card. Finally, download your preferred voicepack language from the [Voice Pack Repo](https://github.com/EdgeTX/edgetx-sdcard-sounds/releases), unzip the archive, and add it to your SD card. - -Alternately, tools like [Flasher](https://github.com/EdgeTX/flasher) and [Buddy](https://buddy.edgetx.org) will do most of this automatically for you with a couple of clicks. diff --git a/sdcard.json b/sdcard.json index dc1afa70..af1eab10 100644 --- a/sdcard.json +++ b/sdcard.json @@ -1,6 +1,7 @@ { "targets": [ ["BETAFPV LiteRadio 3 Pro", "lr3pro-", "bw128x64"], + ["Fatfish F16", "f16-", "c480x272"], ["Flysky EL18", "el18-", "c320x480"], ["Flysky NV14", "nv14-", "c320x480"], ["Flysky PL18", "pl18-", "c480x320"], @@ -22,6 +23,7 @@ ["iFlight Commando 8", "commando8-", "bw128x64"], ["Jumper T12", "t12-", "bw128x64"], ["Jumper T14", "t14-", "bw128x64"], + ["Jumper T15", "t15-", "c480x320"], ["Jumper T16", "t16-", "c480x272"], ["Jumper T18", "t18-", "c480x272"], ["Jumper T20", "t20-", "bw128x64"], diff --git a/sdcard/c480x272/WIDGETS/BattAnalog/main.lua b/sdcard/c480x272/WIDGETS/BattAnalog/main.lua index 97235507..efc66c98 100644 --- a/sdcard/c480x272/WIDGETS/BattAnalog/main.lua +++ b/sdcard/c480x272/WIDGETS/BattAnalog/main.lua @@ -36,7 +36,7 @@ -- Author : Offer Shmuely -- Date: 2021-2023 local app_name = "BattAnalog" -local app_ver = "0.7" +local app_ver = "0.8" local CELL_DETECTION_TIME = 8 @@ -50,14 +50,17 @@ local _options = { -- Data gathered from commercial lipo sensors local percent_list_lipo = { - { { 3.000, 0 }, { 3.093, 1 }, { 3.196, 2 }, { 3.301, 3 }, { 3.401, 4 }, { 3.477, 5 }, { 3.544, 6 }, { 3.601, 7 }, { 3.637, 8 }, { 3.664, 9 }, { 3.679, 10 }, { 3.683, 11 }, { 3.689, 12 }, { 3.692, 13 } }, - { { 3.705, 14 }, { 3.710, 15 }, { 3.713, 16 }, { 3.715, 17 }, { 3.720, 18 }, { 3.731, 19 }, { 3.735, 20 }, { 3.744, 21 }, { 3.753, 22 }, { 3.756, 23 }, { 3.758, 24 }, { 3.762, 25 }, { 3.767, 26 } }, - { { 3.774, 27 }, { 3.780, 28 }, { 3.783, 29 }, { 3.786, 30 }, { 3.789, 31 }, { 3.794, 32 }, { 3.797, 33 }, { 3.800, 34 }, { 3.802, 35 }, { 3.805, 36 }, { 3.808, 37 }, { 3.811, 38 }, { 3.815, 39 } }, - { { 3.818, 40 }, { 3.822, 41 }, { 3.825, 42 }, { 3.829, 43 }, { 3.833, 44 }, { 3.836, 45 }, { 3.840, 46 }, { 3.843, 47 }, { 3.847, 48 }, { 3.850, 49 }, { 3.854, 50 }, { 3.857, 51 }, { 3.860, 52 } }, - { { 3.863, 53 }, { 3.866, 54 }, { 3.870, 55 }, { 3.874, 56 }, { 3.879, 57 }, { 3.888, 58 }, { 3.893, 59 }, { 3.897, 60 }, { 3.902, 61 }, { 3.906, 62 }, { 3.911, 63 }, { 3.918, 64 } }, - { { 3.923, 65 }, { 3.928, 66 }, { 3.939, 67 }, { 3.943, 68 }, { 3.949, 69 }, { 3.955, 70 }, { 3.961, 71 }, { 3.968, 72 }, { 3.974, 73 }, { 3.981, 74 }, { 3.987, 75 }, { 3.994, 76 } }, - { { 4.001, 77 }, { 4.007, 78 }, { 4.014, 79 }, { 4.021, 80 }, { 4.029, 81 }, { 4.036, 82 }, { 4.044, 83 }, { 4.052, 84 }, { 4.062, 85 }, { 4.074, 86 }, { 4.085, 87 }, { 4.095, 88 } }, - { { 4.105, 89 }, { 4.111, 90 }, { 4.116, 91 }, { 4.120, 92 }, { 4.125, 93 }, { 4.129, 94 }, { 4.135, 95 }, { 4.145, 96 }, { 4.176, 97 }, { 4.179, 98 }, { 4.193, 99 }, { 4.200, 100 } }, + { {3.000, 0}}, + { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, + { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, + { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, + { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.818, 40} }, + { {3.822, 41}, {3.825, 42}, {3.829, 43}, {3.833, 44}, {3.836, 45}, {3.840, 46}, {3.843, 47}, {3.847, 48}, {3.850, 49}, {3.854, 50} }, + { {3.857, 51}, {3.860, 52}, {3.863, 53}, {3.866, 54}, {3.870, 55}, {3.874, 56}, {3.879, 57}, {3.888, 58}, {3.893, 59}, {3.897, 60} }, + { {3.902, 61}, {3.906, 62}, {3.911, 63}, {3.918, 64}, {3.923, 65}, {3.928, 66}, {3.939, 67}, {3.943, 68}, {3.949, 69}, {3.955, 70} }, + { {3.961, 71}, {3.968, 72}, {3.974, 73}, {3.981, 74}, {3.987, 75}, {3.994, 76}, {4.001, 77}, {4.007, 78}, {4.014, 79}, {4.021, 80} }, + { {4.029, 81}, {4.036, 82}, {4.044, 83}, {4.052, 84}, {4.062, 85}, {4.074, 86}, {4.085, 87}, {4.095, 88}, {4.105, 89}, {4.111, 90} }, + { {4.116, 91}, {4.120, 92}, {4.125, 93}, {4.129, 94}, {4.135, 95}, {4.145, 96}, {4.176, 97}, {4.179, 98}, {4.193, 99}, {4.200,100} }, } -- from: https://electric-scooter.guide/guides/electric-scooter-battery-voltage-chart/ @@ -85,19 +88,20 @@ local percent_list_lion = { { { 4.10, 100}, { 4.15,100 }, { 4.20, 100} }, } --- TODO new to fine tune the graph! local percent_list_hv = { - { { 3.150, 0 }, { 3.243, 1 }, { 3.346, 2 }, { 3.451, 3 }, { 3.551, 4 }, { 3.627, 5 }, { 3.694, 6 }, { 3.751, 7 }, { 3.787, 8 }, { 3.814, 9 }, { 3.829, 10 }, { 3.833, 11 }, { 3.839, 12 }, { 3.842, 13 } }, - { { 3.855, 14 }, { 3.860, 15 }, { 3.863, 16 }, { 3.865, 17 }, { 3.870, 18 }, { 3.881, 19 }, { 3.885, 20 }, { 3.894, 21 }, { 3.903, 22 }, { 3.906, 23 }, { 3.908, 24 }, { 3.912, 25 }, { 3.917, 26 } }, - { { 3.924, 27 }, { 3.930, 28 }, { 3.933, 29 }, { 3.936, 30 }, { 3.939, 31 }, { 3.944, 32 }, { 3.947, 33 }, { 3.950, 34 }, { 3.952, 35 }, { 3.955, 36 }, { 3.958, 37 }, { 3.961, 38 }, { 3.965, 39 } }, - { { 3.968, 40 }, { 3.972, 41 }, { 3.975, 42 }, { 3.979, 43 }, { 3.983, 44 }, { 3.986, 45 }, { 3.990, 46 }, { 3.993, 47 }, { 3.997, 48 }, { 4.000, 49 }, { 4.004, 50 }, { 4.007, 51 }, { 4.010, 52 } }, - { { 4.013, 53 }, { 4.016, 54 }, { 4.020, 55 }, { 4.024, 56 }, { 4.029, 57 }, { 4.038, 58 }, { 4.043, 59 }, { 4.047, 60 }, { 4.052, 61 }, { 4.056, 62 }, { 4.061, 63 }, { 4.068, 64 } }, - { { 4.073, 65 }, { 4.078, 66 }, { 4.089, 67 }, { 4.093, 68 }, { 4,099, 69 }, { 4.105, 70 }, { 4.111, 71 }, { 4.118, 72 }, { 4.124, 73 }, { 4.131, 74 }, { 4.137, 75 }, { 4.144, 76 } }, - { { 4.151, 77 }, { 4.157, 78 }, { 4.164, 79 }, { 4.171, 80 }, { 4.179, 81 }, { 4.186, 82 }, { 4.194, 83 }, { 4.202, 84 }, { 4.212, 85 }, { 4.224, 86 }, { 4.235, 87 }, { 4.245, 88 } }, - { { 4.255, 89 }, { 4.261, 90 }, { 4.266, 91 }, { 4.270, 92 }, { 4.275, 93 }, { 4.279, 94 }, { 4.285, 95 }, { 4.295, 96 }, { 4.326, 97 }, { 4.329, 98 }, { 4.343, 99 }, { 4.350, 100 } }, + { {3.000, 0}}, + { {3.093, 1}, {3.196, 2}, {3.301, 3}, {3.401, 4}, {3.477, 5}, {3.544, 6}, {3.601, 7}, {3.637, 8}, {3.664, 9}, {3.679, 10} }, + { {3.683, 11}, {3.689, 12}, {3.692, 13}, {3.705, 14}, {3.710, 15}, {3.713, 16}, {3.715, 17}, {3.720, 18}, {3.731, 19}, {3.735, 20} }, + { {3.744, 21}, {3.753, 22}, {3.756, 23}, {3.758, 24}, {3.762, 25}, {3.767, 26}, {3.774, 27}, {3.780, 28}, {3.783, 29}, {3.786, 30} }, + { {3.789, 31}, {3.794, 32}, {3.797, 33}, {3.800, 34}, {3.802, 35}, {3.805, 36}, {3.808, 37}, {3.811, 38}, {3.815, 39}, {3.828, 40} }, + { {3.832, 41}, {3.836, 42}, {3.841, 43}, {3.846, 44}, {3.850, 45}, {3.855, 46}, {3.859, 47}, {3.864, 48}, {3.868, 49}, {3.873, 50} }, + { {3.877, 51}, {3.881, 52}, {3.885, 53}, {3.890, 54}, {3.895, 55}, {3.900, 56}, {3.907, 57}, {3.917, 58}, {3.924, 59}, {3.929, 60} }, + { {3.936, 61}, {3.942, 62}, {3.949, 63}, {3.957, 64}, {3.964, 65}, {3.971, 66}, {3.984, 67}, {3.990, 68}, {3.998, 69}, {4.006, 70} }, + { {4.015, 71}, {4.024, 72}, {4.032, 73}, {4.042, 74}, {4.050, 75}, {4.060, 76}, {4.069, 77}, {4.078, 78}, {4.088, 79}, {4.098, 80} }, + { {4.109, 81}, {4.119, 82}, {4.130, 83}, {4.141, 84}, {4.154, 85}, {4.169, 86}, {4.184, 87}, {4.197, 88}, {4.211, 89}, {4.220, 90} }, + { {4.229, 91}, {4.237, 92}, {4.246, 93}, {4.254, 94}, {4.264, 95}, {4.278, 96}, {4.302, 97}, {4.320, 98}, {4.339, 99}, {4.350,100} }, } - local voltageRanges_lipo = {4.3,8.6,12.9,17.2,21.5,25.8,30.1,34.4,38.7,43.0,47.3,51.6} local voltageRanges_lion = {4.2,8.4,12.6,16.8,21,25.2,29.4,33.6,37.8,42,46.2,50.4,54.6} local voltageRanges_hv = {4.45,8.9,13.35,17.8,22.25,26.7,31.15,35.6,40.05,44.5,48.95,53.4,57.85} diff --git a/sdcard/c480x272/WIDGETS/Flights/lib_flights_history.lua b/sdcard/c480x272/WIDGETS/Flights/lib_flights_history.lua index 3b1b40f1..aee77f90 100644 --- a/sdcard/c480x272/WIDGETS/Flights/lib_flights_history.lua +++ b/sdcard/c480x272/WIDGETS/Flights/lib_flights_history.lua @@ -33,12 +33,6 @@ function M.writeHeaderIfNeeded() return end - -- write csv header - local hFile = io.open(hist_file_name, "a") - if hFile == nil then - M.m_log.info("failed to write file, probably dir is not exist: %s", hist_file_name) - return - end local headline = string.format(line_format, "flight_date", "model_name", @@ -46,10 +40,16 @@ function M.writeHeaderIfNeeded() "duration", "model_id" ) + + -- write csv header + local hFile = io.open(hist_file_name, "a") + if hFile == nil then + M.m_log.info("failed to write file, probably dir is not exist: %s", hist_file_name) + return + end io.write(hFile, headline) local ver_line = "# api_ver=1\n" io.write(hFile, ver_line) - io.close(hFile) end @@ -58,12 +58,6 @@ function M.addFlightLog(flight_start_date_time, duration, flight_count) M.writeHeaderIfNeeded() - local hFile = io.open(hist_file_name, "a") - if hFile == nil then - M.m_log.info("failed to write file, probably dir is not exist: %s", hist_file_name) - return - end - -- flight_date = local dt = flight_start_date_time local flight_date = string.format("%04d-%02d-%02d %02d:%02d", dt.year, dt.mon, dt.day, dt.hour, dt.min) @@ -82,8 +76,13 @@ function M.addFlightLog(flight_start_date_time, duration, flight_count) model_id ) m_log.info("adding flight history line to csv: [%s]", line) - io.write(hFile, line) + local hFile = io.open(hist_file_name, "a") + if hFile == nil then + M.m_log.info("failed to write file, probably dir is not exist: %s", hist_file_name) + return + end + io.write(hFile, line) io.close(hFile) end diff --git a/sdcard/c480x272/WIDGETS/Flights/main.lua b/sdcard/c480x272/WIDGETS/Flights/main.lua index 98e5a18c..d513ba23 100644 --- a/sdcard/c480x272/WIDGETS/Flights/main.lua +++ b/sdcard/c480x272/WIDGETS/Flights/main.lua @@ -54,7 +54,7 @@ ]] local app_name = "Flights" -local app_ver = "1.1" +local app_ver = "1.3" ------------------------------------------------------------------------------------------------------------------ @@ -68,7 +68,7 @@ local enable_count_announcement_on_end = 1 -- 0=no voice, 1=play the count upo local enable_dbg_dots = 1 -- 0=do not show dots, 1=show dbg dots local use_telemetry = 1 -- 0=do not use telemetry, 1=use telemetry in state machine local use_flights_history = 1 -- 0=do not write flights-history, 1=write flights-history -local inverted_arm_switch_logic = 1 -- 0=armed when SF down, 1=armed when SF up +-- local inverted_arm_switch_logic = 1 -- 0=armed when SF down, 1=armed when SF up ------------------------------------------------------------------------------------------------------------------ @@ -84,7 +84,7 @@ local function getSwitchIds(key) ["2.7"] = {SA=112, SB=113, SC=114, SD=115, SE=116, SF=117, CH3 = 204}, ["2.8"] = {SA=120, SB=121, SC=122, SD=123, SE=124, SF=125, CH3 = 212}, ["2.9"] = {SA=120, SB=121, SC=122, SD=123, SE=124, SF=125, CH3 = 212}, - ["2.10"] = {SA=127, SB=128, SC=129, SD=130, SE=131, SF=132, CH3 = 229}, + ["2.10"] = {SA=126, SB=127, SC=128, SD=129, SE=130, SF=131, CH3 = 228}, } local ver, radio, maj, minor, rev, osname = getVersion() local os1 = string.format("%d.%d", maj, minor) @@ -95,12 +95,13 @@ local DEFAULT_ARM_SWITCH_ID = getSwitchIds("SF") -- arm/safety switch=SF local DEFAULT_MOTOR_CHANNEL_ID = getSwitchIds("CH3") -- motor_channel=CH3 local options = { - { "switch", SOURCE, DEFAULT_ARM_SWITCH_ID }, + { "arm_switch", SOURCE, DEFAULT_ARM_SWITCH_ID }, { "motor_channel", SOURCE, DEFAULT_MOTOR_CHANNEL_ID }, { "min_flight_duration", VALUE, default_flight_starting_duration, 2, 120 }, --{ "enable_sounds" , BOOL , 1 }, -- enable sound on adding succ flight, and on end of flight { "text_color", COLOR, COLOR_THEME_PRIMARY2 }, --{ "debug", BOOL, 0 } -- show status on screen + { "non_invert_arm_switch", BOOL, 0 } -- 0=armed when SF down, 1=armed when SF up } local function log(fmt, ...) @@ -133,17 +134,17 @@ local function update(wgt, options) wgt.status.heli_mode = false -- ignore motor direction detection, and throttle position --log("TimerNumB:" .. options.Timer) - if (wgt.options.switch == nil) then - wgt.options.switch = "sf" + if (wgt.options.arm_switch == nil) then + wgt.options.arm_switch = "sf" end - --log("wgt.options.switch: " .. wgt.options.switch) - local fi_sw = getFieldInfo(wgt.options.switch) + local fi_sw = getFieldInfo(wgt.options.arm_switch) if (fi_sw == nil) then wgt.status.switch_name = "--" else wgt.status.switch_name = fi_sw.name end + log("wgt.options.arm_switch: %s, name: %s", wgt.options.arm_switch, wgt.status.switch_name) local fi_mot = getFieldInfo(wgt.options.motor_channel) if (fi_mot == nil) then @@ -157,7 +158,7 @@ local function update(wgt, options) log("1111 is_debug: %s, wgt.zone.h: %s", wgt.options.is_debug, wgt.zone.h) -- for heli, if the motor-sw==switch-sw, then ignore motor direction detection - if (wgt.options.switch == wgt.options.motor_channel) then + if (wgt.options.arm_switch == wgt.options.motor_channel) then wgt.status.heli_mode = true end @@ -272,17 +273,17 @@ local function updateMotorStatus(wgt) end local function updateSwitchStatus(wgt) - local sw_val = getValue(wgt.options.switch) - if inverted_arm_switch_logic == 1 then + local sw_val = getValue(wgt.options.arm_switch) + if wgt.options.non_invert_arm_switch == 0 then wgt.status.switch_on = (sw_val < 0) else wgt.status.switch_on = (sw_val > 0) end --if wgt.status.switch_on then - -- log(string.format("switch status (%s): =ON", wgt.status.switch_name)) + -- log(string.format("arm_switch status (%s): =ON", wgt.status.switch_name)) --else - -- log(string.format("switch status (%s): =OFF", wgt.status.switch_name)) + -- log(string.format("arm_switch status (%s): =OFF", wgt.status.switch_name)) --end end @@ -546,14 +547,14 @@ local function refresh(wgt, event, touchState) if wgt.options.is_debug == true then local dx = 15 --lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 25, string.format("DEBUG:"), SMLSIZE) - lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 20, string.format("%s - switch (%s)", ternary(wgt.status.switch_on), wgt.status.switch_name), SMLSIZE) + lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 20, string.format("%s - arm_switch (%s)", ternary(wgt.status.switch_on), wgt.status.switch_name), SMLSIZE) if (wgt.status.heli_mode == false) then lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 35, string.format("%s - throttle (%s) (inv: %s)", ternary(wgt.status.motor_active), wgt.status.motor_channel_name, wgt.status.motor_channel_direction_inv), SMLSIZE) else lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 35, string.format("%s - throttle (%s) (heli mode)", ternary(wgt.status.motor_active), wgt.status.motor_channel_name), SMLSIZE) end lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 50, string.format("%s - telemetry(%s)", ternary(wgt.status.tele_is_available), wgt.tools.tele_src_name), SMLSIZE) - lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 65, string.format("duration: %.1f/%d", wgt.status.duration_passed / 1000, wgt.tools.getDurationMili(wgt.status.periodic1) / 1000), SMLSIZE) + lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 65, string.format("timer: %.1f/%d", wgt.status.duration_passed / 1000, wgt.tools.getDurationMili(wgt.status.periodic1) / 1000), SMLSIZE) lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 80, string.format("flight duration: %.1f", wgt.status.flight_duration), SMLSIZE) --lcd.drawText(wgt.zone.x + dx, wgt.zone.y + 110, string.format("state: %s", wgt.status.flight_state), 0) diff --git a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua index 3a65b9f8..001c3f9a 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua @@ -289,8 +289,10 @@ function M.newGUI() local guiFocus = not gui.parent or (focused and gui.parent.editing) for idx, element in ipairs(_.elements) do -- Clients may provide an update function for elements - if element.onUpdate then + if element.onUpdate then -- New name for method element.onUpdate(element) + elseif element.update then -- For backward compatibility + element.update(element) end if not element.hidden then element.draw(_.focus == idx and guiFocus) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/brkcrv.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/brkcrv.lua index 071de004..d8ba7c2b 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/brkcrv.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/brkcrv.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k.lua index 7592147a..b96ca2ae 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = DBLSIZE local colors = libGUI.colors local activeGUI @@ -40,7 +40,7 @@ local RGT = LCD_W - 18 local TOP = 50 local BOTTOM = LCD_H - 30 local LINE = 60 -local LINE2 = 28 +local LINE2 = 28 local HEIGHT = 42 local HEIGHT2 = 18 local BUTTON_W = 86 @@ -82,7 +82,7 @@ local state -- Current program state local scores = { } -- List of saved scores local taskWindow = 0 -- Task window duration (zero counts up) local launches = -1 -- Number of launches allowed, -1 for unlimited -local taskScores = 0 -- Number of scores in task +local taskScores = 0 -- Number of scores in task local finalScores = false -- Task scores are final local targetType = 0 -- 1. Huge ladder, 2. Poker, 3. "1234", 4. Big ladder, Else: constant time local scoreType -- 1. Best, 2. Last, 3. Make time @@ -111,7 +111,7 @@ local SCORE_FILE = "/LOGS/JF F3K Scores.csv" -- Handle transitions between program states local function GotoState(newState) state = newState - + -- Stop blinking screenTask.timer0.blink = false @@ -126,13 +126,13 @@ local function GotoState(newState) setStickySwitch(LS_FLT_TMR, false) screenTask.labelTimer0.title = "Target:" screenTask.locked = true - + elseif state == STATE_FLYING then setStickySwitch(LS_WIN_TMR, true) setStickySwitch(LS_FLT_TMR, true) screenTask.labelTimer0.title = "Flight:" screenTask.locked = true - + if model.getTimer(0).start > 0 then -- Report the target time playDuration(model.getTimer(0).start, 0) @@ -140,35 +140,35 @@ local function GotoState(newState) -- ... or beep playTone(1760, 100, PLAY_NOW) end - + elseif state == STATE_COMMITTED then -- Call launch height if getLogicalSwitchValue(LS_ALT) then playNumber(getValue("Alt+"), UNIT_METERS) end - - if launches > 0 then + + if launches > 0 then launches = launches - 1 end - + lastChange = 0 - + elseif state == STATE_FINISHED then playTone(880, 1000, 0) end - + -- Configure "button3" screenTask.button3.disabled = false if state <= STATE_PAUSE then - screenTask.button3.title = "Start" + screenTask.button3.title = "Start" elseif state == STATE_WINDOW then screenTask.button3.title = "Pause" elseif state >= STATE_COMMITTED then screenTask.button3.title = "Zero" else - screenTask.button3.disabled = true + screenTask.button3.disabled = true end - + -- Configure info text label if state == STATE_PAUSE then screenTask.labelInfo.title = string.format("Total: %i sec.", totalScore) @@ -188,18 +188,18 @@ end -- GotoState() -- Function for setting up a task local function SetupTask(taskName, taskData) screenTask.title = taskName - + taskWindow = taskData[1] launches = taskData[2] taskScores = taskData[3] finalScores = taskData[4] targetType = taskData[5] scoreType = taskData[6] - screenTask.buttonQR.value = taskData[7] + screenTask.buttonQR.value = taskData[7] scores = { } totalScore = 0 pokerCalled = false - + -- Setup scores for i = 1, N_LINES do if i > taskScores then @@ -210,7 +210,7 @@ local function SetupTask(taskName, taskData) screenTask.scores[i].hidden = false end end - + -- A few extra counts in 1234 if targetType == 3 then counts = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30, 45, 65, 70, 75, 125, 130, 135, 185, 190, 195} @@ -236,7 +236,7 @@ local function RecordBest(scores, newScore) if newScore > scores[i] then j = i end i = i + 1 end - + if j == 0 then j = i end -- New score is smallest; end of the list end @@ -298,7 +298,7 @@ local function Score() end - totalScore = 0 + totalScore = 0 for i = 1, #scores do totalScore = totalScore + math.min(MaxScore(i), scores[i]) end @@ -307,7 +307,7 @@ end -- Score() -- Reset altimeter local function ResetAlt() for i = 0, 31 do - if model.getSensor(i).name == "Alt" then + if model.getSensor(i).name == "Alt" then model.resetSensor(i) break end @@ -370,11 +370,11 @@ end -- Best1234Target(..) -- Get called time from user in Poker local function PokerCall() local dial - + -- Find dials for setting target time in Poker and height ceilings etc. for input = 0, 31 do local tbl = model.getInput(input, 0) - + if tbl and tbl.name == "Dial" then dial = tbl.source end @@ -393,24 +393,24 @@ local function PokerCall() t2 = tblStep[i + 1][1] end local dt = tblStep[i][2] - + local result = t1 + dt * math.floor(x * (t2 - t1) /dt) - + if scoreType == 3 then result = math.min(winTimer - 1, result) end - + if math.abs(input - lastInput) >= 20 then lastInput = input lastChange = getTime() end - + if state == STATE_COMMITTED and lastChange > 0 and getTime() - lastChange > 100 then playTone(3000, 100, PLAY_NOW) playDuration(result) lastChange = 0 end - + return result end -- PokerCall() @@ -435,11 +435,11 @@ local function TargetTime() return MaxScore(#scores + 1) end end -- TargetTime() - + -- Initialize variables before flight local function InitializeFlight() local targetTime = TargetTime() - + -- Get ready to count down countIndex = #counts while countIndex > 1 and counts[countIndex] >= targetTime do @@ -468,7 +468,7 @@ function widget.background() ResetAlt() end - + -- Call altitude every 10 sec. if getLogicalSwitchValue(LS_ALT10) and now > nextCall then playNumber(getValue("Alt"), UNIT_METERS) @@ -478,11 +478,11 @@ function widget.background() if state <= STATE_READY and state ~= STATE_FINISHED then InitializeFlight() end - + flightTimer = model.getTimer(0).value flightTime = math.abs(model.getTimer(0).start - flightTimer) winTimer = model.getTimer(1).value - + if state < STATE_WINDOW then if state == STATE_IDLE then -- Set window timer @@ -515,7 +515,7 @@ function widget.background() -- Play tone to warn that timer is NOT running playTone(1760, 200, 0, PLAY_NOW) end - + elseif state == STATE_READY then if launchReleased then GotoState(STATE_FLYING) @@ -533,12 +533,12 @@ function widget.background() elseif flightTimer > 0 and math.ceil(flightTimer / 60) < math.ceil(prevFt / 60) then playDuration(flightTimer, 0) end - + -- Blink when flight ttimer is negative if flightTimer < 0 then screenTask.timer0.blink = true end - + if state == STATE_FLYING then -- Within 10 sec. "grace period", cancel the flight if launchPulled then @@ -549,7 +549,7 @@ function widget.background() if flightTime >= 10 then GotoState(STATE_COMMITTED) end - + elseif launchPulled then -- Report the time after flight is done if model.getTimer(0).start == 0 then @@ -557,7 +557,7 @@ function widget.background() end Score() - + -- Change state if (finalScores and #scores == taskScores) or launches == 0 or (taskWindow > 0 and winTimer <= 0) then GotoState(STATE_FINISHED) @@ -568,7 +568,7 @@ function widget.background() end end end - + prevWt = winTimer prevFt = flightTimer end @@ -622,12 +622,12 @@ function libGUI.widgetRefresh() local COL2 = COL1 + 30 local COL3 = COL1 + 125 local RGT = COL1 + 400 - + -- Draw scores x = 5 local y = 0 local dy = widget.zone.h / N_LINES - + for i = 1, taskScores do lcd.drawText(COL1, y, string.format("%i.", i), colors.primary1 + DBLSIZE) if i > #scores then @@ -641,14 +641,14 @@ function libGUI.widgetRefresh() -- Draw timers local blink = 0 local y = 4 - + local tmr = model.getTimer(0).value - if tmr < 0 and state == STATE_COMMITTED then + if tmr < 0 and state == STATE_COMMITTED then blink = BLINK end lcd.drawText(COL3, y + 10, screenTask.labelTimer0.title, colors.primary1 + DBLSIZE) - lcd.drawTimer(RGT, y, tmr, colors.primary1 + blink + XXLSIZE + RIGHT) + lcd.drawTimer(RGT, y, tmr, colors.primary1 + blink + XXLSIZE + RIGHT) y = y + 2 * dy tmr = model.getTimer(1).value lcd.drawText(COL3, y + 10, "Task:", colors.primary1 + DBLSIZE) @@ -660,13 +660,13 @@ end -- widgetRefresh() local function SetupScreen(gui, title, pop) gui.title = title local x1 - + if pop then x1 = LCD_W - 80 else x1 = LCD_W - 50 end - + function gui.fullScreenRefresh() local color lcd.clear(COLOR_THEME_SECONDARY3) @@ -678,17 +678,17 @@ local function SetupScreen(gui, title, pop) -- Date local now = getDateTime() local str = string.format("%02i:%02i", now.hour, now.min) - lcd.drawText(x1, 6, str, RIGHT + MIDSIZE + colors.primary2) + lcd.drawText(x1, 6, str, RIGHT + MIDSIZE + colors.primary2) if soarGlobals.battery == 0 then color = COLOR_THEME_DISABLED else color = colors.primary2 end - + str = string.format("%1.1fV", soarGlobals.battery) lcd.drawText(x1 - 65, 6, str, RIGHT + MIDSIZE + color) - + -- Draw trims local p = { { LCD_W - 191, LCD_H - 14, 177, 8 }, @@ -696,7 +696,7 @@ local function SetupScreen(gui, title, pop) { LCD_W - 14, 68, 8, 177 }, { 7, 68, 8, 177 }, } - + for i = 1, 4 do local q = p[i] local value = getValue(trimSources[i]) / 10.24 @@ -708,21 +708,21 @@ local function SetupScreen(gui, title, pop) x = q[1] + q[3] / 2 y = q[2] + q[4] * (100 - value) / 200 end - + lcd.drawFilledRectangle(q[1], q[2], q[3], q[4], COLOR_THEME_SECONDARY1) lcd.drawFilledRectangle(x - 9, y - 6, 18, 15, colors.primary1) lcd.drawFilledRectangle(x - 10, y - 7, 18, 15, colors.focus) lcd.drawNumber(x, y, value, SMLSIZE + VCENTER + CENTER + colors.primary2) end - + -- Flight mode - lcd.drawText(LCD_W / 2, LCD_H - LINE2, select(2, getFlightMode()), MIDSIZE + CENTER + COLOR_THEME_SECONDARY1) + lcd.drawText(LCD_W / 2, LCD_H - LINE2, select(2, getFlightMode()), MIDSIZE + CENTER + COLOR_THEME_SECONDARY1) end -- fullScreenRefresh() - + -- Return button if pop then gui.buttonRet = gui.custom({ }, LCD_W - 74, 6, 28, 28) - + function gui.buttonRet.draw(focused) local color @@ -733,14 +733,14 @@ local function SetupScreen(gui, title, pop) color = COLOR_THEME_DISABLED gui.buttonRet.disabled = true end - + lcd.drawRectangle(LCD_W - 74, 6, 28, 28, color) lcd.drawFilledRectangle(LCD_W - 61, 12, 3, 18, color) for i = 0, 3 do lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 50 - i, 20, SOLID, color) lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 70 + i, 20, SOLID, color) end - + if focused then gui.buttonRet.drawFocus() end @@ -752,7 +752,7 @@ local function SetupScreen(gui, title, pop) end end end - + -- Minimize button local buttonMin = gui.custom({ }, LCD_W - 34, 6, 28, 28) @@ -764,7 +764,7 @@ local function SetupScreen(gui, title, pop) buttonMin.drawFocus() end end - + function buttonMin.onEvent(event) if event == EVT_VIRTUAL_ENTER then lcd.exitFullScreen() @@ -780,7 +780,7 @@ local function SetupScreen(gui, title, pop) end end gui.setEventHandler(EVT_VIRTUAL_EXIT, HandleEXIT) - + return gui end -- SetupScreen @@ -791,7 +791,7 @@ do local ROW = 65 x = 40 y = 60 - + SetupScreen(menuMain, "SoarETX F3K") -- Generate callbacks with closure for calling submenus @@ -806,7 +806,7 @@ do menuMain.button(x, y, WIDTH, HEIGHT, "Practice", MakePush(menuPractice)) y = y + ROW menuMain.button(x, y, WIDTH, HEIGHT, "Scores", MakePush(menuScores)) - + activeGUI = menuMain end @@ -831,7 +831,7 @@ do -- Setup F3K tasks menu "L. One flight only", "M. Huge Ladder" } - + -- {win, launches, scores, final, tgtType, scoType, QR } local taskData = { { 420, -1, 1, false, 300, 2, false }, -- A. Last flight @@ -862,7 +862,7 @@ end do -- Setup practice tasks menu SetupScreen(menuPractice, "Practice Tasks", true) - + local tasks = { "Just Fly!", "Quick Relaunch!", @@ -875,7 +875,7 @@ do -- Setup practice tasks menu { 0, -1, 5, false, 2, 2, true }, -- QR { 600, 2, 2, true, 5, 2, false } -- Deuces } - + -- Call back function running when a menu item is selected local function callBack(menu) SetupTask(tasks[menu.selected], taskData[menu.selected]) @@ -888,13 +888,13 @@ end do -- Setup score keeper screen for F3K and Practice tasks SetupScreen(screenTask, "", true) - + -- Restore default task and dismiss task screen - function screenTask.dismiss() + function screenTask.dismiss() SetupTask("Just Fly!", { 0, -1, 5, false, 0, 2, false }) PopGUI() end - + -- Return button shows prompt to save scores instead of popping right away function screenTask.buttonRet.onEvent(event) if event == EVT_VIRTUAL_ENTER then @@ -905,17 +905,17 @@ do -- Setup score keeper screen for F3K and Practice tasks end end end - + -- Add score times local y = TOP local dy = select(2, lcd.sizeText("", libGUI.flags)) - + screenTask.scoreLabels = { } screenTask.scores = { } for i = 1, N_LINES do screenTask.scoreLabels[i] = screenTask.label(LEFT, y, 20, HEIGHT, string.format("%i.", i)) - + local s = screenTask.timer(LEFT + 40, y, 60, HEIGHT, 0, nil) s.disabled = true s.value = "- - -" @@ -924,35 +924,35 @@ do -- Setup score keeper screen for F3K and Practice tasks -- Modify timer's draw function to insert score value local draw = s.draw function s.draw(idx) - if i > #scores then + if i > #scores then screenTask.scores[i].value = "- - -" else screenTask.scores[i].value = scores[i] end - + draw(idx) end y = y + dy end - + -- Add center buttons local y = TOP screenTask.buttonQR = screenTask.toggleButton(COL2, y, BUTTON_W, HEIGHT, "QR", false, nil) y = y + LINE screenTask.buttonEoW = screenTask.toggleButton(COL2, y, BUTTON_W, HEIGHT, "EoW", true, nil) - + local function callBack(button) if state <= STATE_PAUSE then GotoState(STATE_WINDOW) - + elseif state == STATE_WINDOW then GotoState(STATE_PAUSE) - + elseif state >= STATE_COMMITTED then -- Record a zero score! flightTime = 0 Score() - + -- Change state if winTimer <= 0 or (finalScores and #scores == taskScores) or launches == 0 then GotoState(STATE_FINISHED) @@ -962,26 +962,26 @@ do -- Setup score keeper screen for F3K and Practice tasks end end end - + y = y + LINE screenTask.button3 = screenTask.button(COL2, y, BUTTON_W, HEIGHT, "Start", callBack) - + -- Info text label screenTask.labelInfo = screenTask.label(RGT - 250, BOT_ROW, 250, HEIGHT, "", libGUI.flags + RIGHT) - + -- Add timers y = TOP screenTask.labelTimer0 = screenTask.label(RGT - 160, y, 50, HEIGHT2, "Target:", MIDSIZE) y = y + LINE2 screenTask.timer0 = screenTask.timer(RGT - 160, y, 160, HEIGHT, 0, nil, XXLSIZE + RIGHT) screenTask.timer0.disabled = true - + y = y + LINE screenTask.label(RGT - 160, y, 50, HEIGHT2, "Task:", MIDSIZE) y = y + LINE2 local tmr = screenTask.timer(RGT - 160, y, 160, HEIGHT, 1, nil, XXLSIZE + RIGHT) tmr.disabled = true - + -- Short press EXIT handler must prompt to save scores local function HandleEXIT(event, touchState) if CanPopGUI() then @@ -1001,7 +1001,7 @@ do -- Prompt asking to save scores and exit task window local RGT = x0 + PROMPT_W - PROMPT_M local TOP = y0 + PROMPT_M local BOTTOM = y0 + PROMPT_H - PROMPT_M - + function promptSaveScores.fullScreenRefresh() lcd.drawFilledRectangle(x0, y0, PROMPT_W, PROMPT_H, colors.primary2) lcd.drawRectangle(x0, y0, PROMPT_W, PROMPT_H, colors.primary1, 3) @@ -1015,21 +1015,21 @@ do -- Prompt asking to save scores and exit task window if scoreFile then io.write(scoreFile, string.format("%s,%s", model.getInfo().name, screenTask.title)) - local now = getDateTime() + local now = getDateTime() io.write(scoreFile, string.format(",%04i-%02i-%02i", now.year, now.mon, now.day)) - io.write(scoreFile, string.format(",%02i:%02i", now.hour, now.min)) + io.write(scoreFile, string.format(",%02i:%02i", now.hour, now.min)) io.write(scoreFile, string.format(",s,%i", taskScores)) io.write(scoreFile, string.format(",%i", totalScore)) - + for i = 1, #scores do io.write(scoreFile, string.format(",%i", scores[i])) end - + io.write(scoreFile, "\n") io.close(scoreFile) end end - + -- Dismiss prompt and return to menu screenTask.dismissPrompt() screenTask.dismiss() @@ -1047,7 +1047,7 @@ do -- Setup score browser screen local scoreFile -- File handle local pos -- Read position in file local firstRecordTouch -- First record at the start of touch slide - + -- Read a line of a log file local function ReadLine(scoreFile, pos) if scoreFile and pos then @@ -1061,7 +1061,7 @@ do -- Setup score browser screen return pos, str end end - + -- No "\n" was found; return nothing return 0, "" end -- ReadLine() @@ -1074,7 +1074,7 @@ do -- Setup score browser screen for field in string.gmatch(str, "[^,]+") do i = i + 1 - + if i == 1 then record.planeName = field elseif i == 2 then @@ -1093,33 +1093,33 @@ do -- Setup score browser screen record.scores[#record.scores + 1] = tonumber(field) end end - + if record.totalScore then records[#records + 1] = record end end -- ReadLineData() - + local function DrawRecord(i, r) local top = 40 + i * RECORD_H local left = 200 local w = (LCD_W - left - 10) / 3 local record = records[r] - + if not record then return end - + if r % 2 == 0 then lcd.drawFilledRectangle(0, top, LCD_W, RECORD_H, COLOR_THEME_SECONDARY2) end - + lcd.drawText(10, top + 6, record.taskName, BOLD) lcd.drawText(10, top + 24, record.dateStr .. " " .. record.timeStr, SMLSIZE) lcd.drawText(10, top + 36, record.planeName, SMLSIZE) - + local x = left local y = top + 6 - + for j = 1, math.min(5, record.taskScores) do lcd.drawText(x, y, j .. ".") @@ -1130,7 +1130,7 @@ do -- Setup score browser screen else lcd.drawText(x + 18, y, record.scores[j] .. record.unitStr) end - + if j == 3 then x = left y = top + 30 @@ -1138,7 +1138,7 @@ do -- Setup score browser screen x = x + w end end - + lcd.drawText(left + 2 * w, top + 30, "Total: " .. record.totalScore .. record.unitStr) end -- DrawRecord @@ -1152,7 +1152,7 @@ do -- Setup score browser screen end lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, "Score Card", bit32.bor(DBLSIZE, colors.primary2)) @@ -1160,25 +1160,25 @@ do -- Setup score browser screen -- Date local now = getDateTime() local str = string.format("%02i:%02i", now.hour, now.min) - lcd.drawText(LCD_W - 80, 6, str, RIGHT + MIDSIZE + colors.primary2) + lcd.drawText(LCD_W - 80, 6, str, RIGHT + MIDSIZE + colors.primary2) if soarGlobals.battery == 0 then color = COLOR_THEME_DISABLED else color = colors.primary2 end - + str = string.format("%1.1fV", soarGlobals.battery) lcd.drawText(LCD_W - 140, 6, str, RIGHT + MIDSIZE + color) - + -- Return button lcd.drawFilledRectangle(LCD_W - 74, 6, 28, 28, COLOR_THEME_SECONDARY1) lcd.drawRectangle(LCD_W - 74, 6, 28, 28, colors.primary2) - + for i = -1, 1 do lcd.drawLine(LCD_W - 60 + i, 12, LCD_W - 60 + i, 30, SOLID, colors.primary2) end - + for i = 0, 3 do lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 50 - i, 20, SOLID, colors.primary2) lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 70 + i, 20, SOLID, colors.primary2) @@ -1188,17 +1188,17 @@ do -- Setup score browser screen lcd.drawFilledRectangle(LCD_W - 34, 6, 28, 28, COLOR_THEME_SECONDARY1) lcd.drawRectangle(LCD_W - 34, 6, 28, 28, colors.primary2) lcd.drawFilledRectangle(LCD_W - 30, 19, 20, 3, colors.primary2) - + if event ~= EVT_TOUCH_SLIDE then firstRecordTouch = nil end - + if event == EVT_VIRTUAL_EXIT then firstRecord = nil return PopGUI() elseif event == EVT_TOUCH_TAP then local x, y = touchState.x, touchState.y - + if 6 <= y and y <= 34 then if LCD_W - 74 <= x and x <= LCD_W - 40 then firstRecord = nil @@ -1226,9 +1226,9 @@ do -- Setup score browser screen local r = i + firstRecord DrawRecord(i, r) end - + else -- Read score records - lcd.drawText(LCD_W / 2, LCD_H / 2, "Reading scores ...", VCENTER + CENTER + DBLSIZE + colors.primary1) + lcd.drawText(LCD_W / 2, LCD_H / 2, "Reading scores ...", VCENTER + CENTER + DBLSIZE + colors.primary1) if not scoreFile then scoreFile = io.open(SCORE_FILE, "r") @@ -1237,7 +1237,7 @@ do -- Setup score browser screen records = { } end end - + if scoreFile then for i = 1, 10 do local str @@ -1247,11 +1247,11 @@ do -- Setup score browser screen io.close(scoreFile) scoreFile = nil firstRecord = math.max(1, #records - 3) - + if #records == 0 then firstRecord = nil end - + break end end diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_ctr.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_ctr.lua index 510ee947..2c7c49a0 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_ctr.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_ctr.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors @@ -84,9 +84,9 @@ end local function adjust(slider) -- Compensate for possible negative differential local dif = model.getGlobalVariable(GV_DIF, 0) - local difComp = 100.0 / math.max(10.0, math.min(100.0, 100.0 + dif)) + local difComp = 100.0 / math.max(10.0, math.min(100.0, 100.0 + dif)) -- Calculate aileron travel from current airbrake travel - local ail = math.min(2 * slider.value, 2 * (100 - slider.value) * difComp) + local ail = math.min(2 * slider.value, 2 * (100 - slider.value) * difComp) model.setGlobalVariable(GV_AIL, 0, ail) model.setGlobalVariable(GV_BRK, 0, slider.value) end @@ -96,7 +96,7 @@ end do function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, title, bit32.bor(DBLSIZE, colors.primary2)) @@ -107,12 +107,12 @@ do local brk = 2 * model.getGlobalVariable(GV_BRK, 0) local brkDeg = 0.45 * brk local dif = 0.01 * model.getGlobalVariable(GV_DIF, 0) - + lcd.drawPie(CTR_X, CTR_Y, R2, 90 - ailDeg * math.min(1, 1 + dif), 91 + brkDeg, colors.primary2) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90 + ailDeg * math.min(1, 1 - dif), 90 + brkDeg, COLOR_THEME_SECONDARY2) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90, 90 + ailDeg * math.min(1, 1 - dif), COLOR_BLEND) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90 - ailDeg * math.min(1, 1 + dif), 90, COLOR_THEME_SECONDARY1) - + lcd.drawArc(CTR_X, CTR_Y, R2, 90 - ailDeg * math.min(1, 1 + dif), 90 + brkDeg, colors.primary3) drawRadian(90 - ailDeg * math.min(1, 1 + dif)) drawRadian(90) @@ -132,7 +132,7 @@ do lcd.drawText(CTR_X + R1, CTR_Y, "brake ", RIGHT + SMLSIZE + colors.primary1) lcd.drawText(CTR_X + R2, CTR_Y - SML_H, " max.", SMLSIZE + colors.primary1) lcd.drawText(CTR_X + R2, CTR_Y, " reflex", SMLSIZE + colors.primary1) - + local txt = "Use the slider to adjust the flaperons to the position of maximum reflex.\n\n" .. "Notice that camber can only move the flaperons down from this position." lcd.drawTextLines(MARGIN, TOP, CTR_X - 2 * MARGIN, LCD_H - TOP - MARGIN, txt, colors.primary1) @@ -178,6 +178,6 @@ function widget.refresh(event, touchState) setStickySwitch(LS_CTR, true) slider.value = model.getGlobalVariable(GV_BRK, 0) end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_mix.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_mix.lua index fd8f2904..275c42d7 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_mix.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_mix.lua @@ -20,7 +20,8 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +print(string.format("111 f3k_mix soarGlobals.libGUI: [%s]", soarGlobals.libGUI)) +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors @@ -41,23 +42,23 @@ local W2 = LCD_W2 - 2 * MARGIN - W1 do function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, title, bit32.bor(DBLSIZE, colors.primary2)) - + -- Fligh mode local fmIdx, fmStr = getFlightMode() lcd.drawText(LCD_W - HEADER, HEADER / 2, "FM" .. fmIdx .. ":" .. fmStr, RIGHT + VCENTER + MIDSIZE + colors.primary2) - + -- Line stripes for i = 1, 3, 2 do lcd.drawFilledRectangle(0, HEADER + LINE * i, LCD_W, LINE, COLOR_THEME_SECONDARY2) end - + local bottom = HEADER + 4 * LINE lcd.drawLine(LCD_W2, HEADER, LCD_W2, bottom, SOLID, colors.primary1) - + -- Help text local txt = "Some variables can be adjusted individually for each flight mode.\n" .. "Therefore, select the flight mode for which you want to adjust.\n" .. @@ -85,7 +86,7 @@ do -- Grid for items local x, y = MARGIN, HEADER + 2 - + local function move() if x == MARGIN then x = x + LCD_W2 @@ -94,11 +95,11 @@ do y = y + LINE end end - + -- Add label and number element for a GV local function addGV(label, gv, min, max) gui.label(x, y, W1, HEIGHT, label) - + local function changeGV(delta, number) local value = number.value + delta value = math.max(value, min) @@ -106,16 +107,16 @@ do model.setGlobalVariable(gv, fm, value) return value end - + local number = gui.number(x + W1, y, W2, HEIGHT, 0, changeGV, RIGHT + libGUI.flags) - + function number.update() number.value = model.getGlobalVariable(gv, fm) end - + move() end - + -- ADD GVs addGV("Aileron " .. CHAR_RIGHT .. " rudder", 2, -100, 100) addGV("Differential", 3, -100, 100) @@ -124,7 +125,7 @@ do addGV("Elevator input", 6, 20, 100) addGV("Aileron input", 7, 20, 100) addGV("Exponential", 8, 20, 100) - + -- Add battery warning gui.label(x, y, W1, HEIGHT, "Battery warning level (V)") @@ -135,7 +136,7 @@ do soarGlobals.setParameter(soarGlobals.batteryParameter, value - 100) return value end - + local batP = soarGlobals.getParameter(soarGlobals.batteryParameter) gui.number(x + W1, y, W2, HEIGHT, batP + 100, changeBattery, RIGHT + PREC1 + libGUI.flags) end -- Setup GUI @@ -150,8 +151,8 @@ function widget.refresh(event, touchState) lcd.drawText(widget.zone.w / 2, widget.zone.h / 2, title, CENTER + VCENTER + MIDSIZE + colors.primary2) return end - + fm = getFlightMode() - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_sw.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_sw.lua index 5e2afbee..a2f62a8d 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_sw.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3k_sw.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui local colors = libGUI.colors @@ -50,7 +50,7 @@ local items = { local function init() gui = libGUI.newGUI() - + function gui.fullScreenRefresh() -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) @@ -88,7 +88,7 @@ local function init() -- Build the list of drop downs local y = HEADER + 2 local w1 = COL2 - MARGIN - + -- Build lists of physical switch position indices and names local swIndices = { } local swNames = { } @@ -107,20 +107,20 @@ local function init() lsTbl.v1 = swIdx model.setLogicalSwitch(dropDown.ls, lsTbl) end - + for i, item in ipairs(items) do gui.label(MARGIN, y, w1, HEIGHT, item[1]) - + local swIdx = model.getLogicalSwitch(item[2]).v1 local selected = 0 - + for i, idx in ipairs(swIndices) do if swIdx == idx then selected = i break end end - + if selected == 0 then -- Oops, no switch matching current value in LS! gui.label(COL2, y, WIDTH, HEIGHT, "???", CENTER + BOLD) @@ -148,6 +148,6 @@ function widget.refresh(event, touchState) init() return end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kfh_mx.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kfh_mx.lua index c8568434..0bed2a1c 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kfh_mx.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kfh_mx.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_mx.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_mx.lua index 49aefebb..5fee420f 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_mx.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_mx.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors @@ -41,23 +41,23 @@ local W2 = LCD_W2 - 2 * MARGIN - W1 do function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, title, bit32.bor(DBLSIZE, colors.primary2)) - + -- Fligh mode local fmIdx, fmStr = getFlightMode() lcd.drawText(LCD_W - HEADER, HEADER / 2, "FM" .. fmIdx .. ":" .. fmStr, RIGHT + VCENTER + MIDSIZE + colors.primary2) - + -- Line stripes for i = 1, 3, 2 do lcd.drawFilledRectangle(0, HEADER + LINE * i, LCD_W, LINE, COLOR_THEME_SECONDARY2) end - + local bottom = HEADER + 4 * LINE lcd.drawLine(LCD_W2, HEADER, LCD_W2, bottom, SOLID, colors.primary1) - + -- Help text local txt = "Some variables can be adjusted individually for each flight mode.\n" .. "Therefore, select the flight mode for which you want to adjust.\n" .. @@ -85,7 +85,7 @@ do -- Grid for items local x, y = MARGIN, HEADER + 2 - + local function move() if x == MARGIN then x = x + LCD_W2 @@ -94,11 +94,11 @@ do y = y + LINE end end - + -- Add label and number element for a GV local function addGV(label, gv, min, max) gui.label(x, y, W1, HEIGHT, label) - + local function changeGV(delta, number) local value = number.value + delta value = math.max(value, min) @@ -106,20 +106,20 @@ do model.setGlobalVariable(gv, fm, value) return value end - + local number = gui.number(x + W1, y, W2, HEIGHT, 0, changeGV, RIGHT + libGUI.flags) - + function number.update() number.value = model.getGlobalVariable(gv, fm) end - + move() end - + -- ADD GVs addGV("Elevator input", 6, 20, 100) addGV("Exponential", 8, 20, 100) - + -- Add battery warning gui.label(x, y, W1, HEIGHT, "Battery warning level (V)") @@ -130,7 +130,7 @@ do soarGlobals.setParameter(soarGlobals.batteryParameter, value - 100) return value end - + local batP = soarGlobals.getParameter(soarGlobals.batteryParameter) gui.number(x + W1, y, W2, HEIGHT, batP + 100, changeBattery, RIGHT + PREC1 + libGUI.flags) end -- Setup GUI @@ -145,8 +145,8 @@ function widget.refresh(event, touchState) lcd.drawText(widget.zone.w / 2, widget.zone.h / 2, title, CENTER + VCENTER + MIDSIZE + colors.primary2) return end - + fm = getFlightMode() - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_sw.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_sw.lua index a62e2c2a..235a905b 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_sw.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/f3kre_sw.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui local colors = libGUI.colors @@ -49,7 +49,7 @@ local items = { local function init() gui = libGUI.newGUI() - + function gui.fullScreenRefresh() -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) @@ -87,11 +87,11 @@ local function init() -- Build the list of drop downs local y = HEADER + 2 local w1 = COL2 - MARGIN - + -- Build lists of physical switch position indices and names local swIndices = { } local swNames = { } - + for swIdx, swName in switches() do if string.find(swName,"^!?S[A-H][+-]?") then i = #swIndices + 1 @@ -106,20 +106,20 @@ local function init() lsTbl.v1 = swIdx model.setLogicalSwitch(dropDown.ls, lsTbl) end - + for i, item in ipairs(items) do gui.label(MARGIN, y, w1, HEIGHT, item[1]) - + local swIdx = model.getLogicalSwitch(item[2]).v1 local selected = 0 - + for i, idx in ipairs(swIndices) do if swIdx == idx then selected = i break end end - + if selected == 0 then -- Oops, no switch matching current value in LS! gui.label(COL2, y, WIDTH, HEIGHT, "???", CENTER + BOLD) @@ -147,6 +147,6 @@ function widget.refresh(event, touchState) init() return end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/graph.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/graph.lua index 97bdc70e..95225a0b 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/graph.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/graph.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local colors = libGUI.colors local title = "Graph" diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/outputs.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/outputs.lua index b56b33af..4ff0f7ba 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/outputs.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/outputs.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui local colors = libGUI.colors @@ -75,12 +75,12 @@ do local PROMPT_H = 172 warningPrompt.x = (LCD_W - PROMPT_W) / 2 warningPrompt.y = (LCD_H - PROMPT_H) / 2 - + function warningPrompt.fullScreenRefresh() local txt = "Please disable the motor!\n\n" .. "Sudden spikes may occur when channels are moved.\n\n" .. "Press ENTER to proceed." - + warningPrompt.drawFilledRectangle(0, 0, PROMPT_W, HEADER, COLOR_THEME_SECONDARY1) warningPrompt.drawFilledRectangle(0, HEADER, PROMPT_W, PROMPT_H - HEADER, libGUI.colors.primary2) warningPrompt.drawRectangle(0, 0, PROMPT_W, PROMPT_H, libGUI.colors.primary1, 2) @@ -125,7 +125,7 @@ do } editPrompt.x = (LCD_W - PROMPT_W) / 2 editPrompt.y = (LCD_H - PROMPT_H) / 2 - + function editPrompt.fullScreenRefresh() if not editPrompt.editing then editPoints = 0 @@ -138,7 +138,7 @@ do editPrompt.drawFilledRectangle(0, HEADER, PROMPT_W, PROMPT_H - HEADER, libGUI.colors.primary2) editPrompt.drawRectangle(0, 0, PROMPT_W, PROMPT_H, libGUI.colors.primary1, 2) editPrompt.drawText(MARGIN, HEADER / 2, "Select what to edit:", DBLSIZE + VCENTER + libGUI.colors.primary2) - + local y = HEADER + MARGIN + h / 2 for i = 1, 5 do @@ -162,7 +162,7 @@ do editPoints = menu.selected gui.dismissPrompt() end -- onMenu(...) - + editPrompt.menu(MARGIN, HEADER + MARGIN, MENU_W, MENU_H, menuItems, onMenu) end -- Prompt for selecting what to edit @@ -171,13 +171,13 @@ local function MoveOutput(direction, channel) local m = { } -- Channel indices m[1] = channel.iChannel -- Channel to move m[2] = m[1] + direction -- Neighbouring channel to swap - + -- Are we at then end? if m[2] < 1 or m[2] > N then playTone(3000, 100, 0, PLAY_NOW) return end - + local outputs = { } -- List of output tables local mixes = { } -- List of lists of mixer tables @@ -191,7 +191,7 @@ local function MoveOutput(direction, channel) mixes[i][j] = model.getMix(m[i] - 1, j - 1) end end - + -- Write back swapped data for i = 1, 2 do model.setOutput(m[i] - 1, outputs[3 - i]) @@ -223,7 +223,7 @@ local function MoveOutput(direction, channel) mixes[j].source = m[1] + CHAN_BASE end end - + -- Do we have to write back data? if dirty then -- Delete existing mixer lines @@ -262,7 +262,7 @@ local function init() -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(MARGIN, HEADER / 2 - 2, "Configure outputs", DBLSIZE + VCENTER + colors.primary2) - + -- Row background for i = 0, 6 do local y = HEADER + i * ROW @@ -272,7 +272,7 @@ local function init() lcd.drawFilledRectangle(0, y, LCD_W, ROW, COLOR_THEME_SECONDARY3) end end - + -- Adjust scroll for channels if focusNamed > 0 then if focusNamed < firstLine then @@ -282,7 +282,7 @@ local function init() end end focusNamed = 0 - + -- Draw vertical reference lines for i = -6, 6 do local x = CTR - i * MAXOUT / (SCALE * 6) + 2 @@ -313,10 +313,10 @@ local function init() local HEIGHT = ROW - 8 local iNamed = 0 channels = { } - + for iChannel = 1, N do local output = model.getOutput(iChannel - 1) - + if output and output.name ~= "" then local channel = gui.gui(2, LCD_H, LCD_W - 4, ROW - 4) local d0 @@ -356,8 +356,8 @@ local function init() end -- onEvent(...) -- Custom element for changing output channel (and moving all mixer lines etc.) - local nbrChannel = channel.custom({ }, 2, 2, 30, HEIGHT) - + local nbrChannel = channel.custom({ }, 2, 2, 30, HEIGHT) + function nbrChannel.draw(focused) local fg = libGUI.colors.primary1 if focused then @@ -391,13 +391,13 @@ local function init() channel.editing = true end end -- onEvent(...) - + -- Label for channel name local lblName = channel.label(32, 2, 140, HEIGHT, ". " .. channel.output.name) - + -- Custom element to invert output direction local revert = channel.custom({ }, 172, 2, 30, HEIGHT) - + function revert.draw(focused) local y = HEIGHT / 2 + 3 if channel.output.revert == 1 then @@ -413,7 +413,7 @@ local function init() channel.drawLine(x, y, x - 8, y + 8, SOLID, colors.primary1) end end - + function revert.onEvent(event, touchState) if event == EVT_VIRTUAL_ENTER then channel.output.revert = 1 - channel.output.revert @@ -435,14 +435,14 @@ local function init() local y = HEIGHT / 2 + 2 local yLbl = y - 12 - select(2, lcd.sizeText("", flags)) local iScroll = 0 - + function interval.draw(focused) local output = channel.output local p = { 0, 0, 0 } local colorBar = libGUI.colors.primary3 local colorDot = libGUI.colors.primary2 local colorDotBorder = libGUI.colors.primary3 - + x = { CTR + output.min / SCALE, CTR + output.offset / SCALE, @@ -457,7 +457,7 @@ local function init() channel.drawNumber(x[3], yLbl, 0.1 * output.max, flags) colorBar = libGUI.colors.primary1 colorDot = libGUI.colors.edit - p = activePoints(editPoints) + p = activePoints(editPoints) else interval.drawFocus() end @@ -477,21 +477,21 @@ local function init() -- Draw position indicators local outX = getValue(CHAN_BASE + channel.iChannel) if outX >= 0 then - outX = output.offset + math.min(outX, 1024) * (output.max - output.offset) / 1024 + outX = output.offset + math.min(outX, 1024) * (output.max - output.offset) / 1024 else - outX = output.offset + math.max(outX, -1024) * (output.offset - output.min) / 1024 + outX = output.offset + math.max(outX, -1024) * (output.offset - output.min) / 1024 end outX = CTR + outX / SCALE channel.drawFilledTriangle(outX, y - 3, outX - 3, y - 9, outX + 3, y - 9, colorBar) channel.drawLine(outX, y - 2, outX, y + 2, SOLID, colorBar) channel.drawFilledTriangle(outX, y + 3, outX - 3, y + 9, outX + 3, y + 9, colorBar) end -- draw(...) - + local RR = 14 ^ 2 - + local function ptCovers(p, q) local ap = activePoints(editPoints) - + for i = 1, 3 do if ap[i] ~= 0 and (x[i] - p) ^ 2 + (y - q) ^ 2 <= RR then return i @@ -499,46 +499,46 @@ local function init() end return 0 end -- ptCovers(...) - + local function adjustPoints(d) local output = channel.output local p = activePoints(editPoints) local min = output.min local ctr = output.offset local max = output.max - + -- Check limits if p[1] == -1 then d = math.min(d, math.max(0, MAXOUT + min)) elseif p[1] == 1 then d = math.max(d, math.min(0, -(MAXOUT + min))) end - + if p[2] - p[1] == 1 then d = math.max(d, math.min(0, MINDIF + min - ctr)) elseif p[2] - p[1] == -1 then d = math.min(d, math.max(0, ctr - min - MINDIF)) end - + if p[3] - p[2] == 1 then d = math.max(d, math.min(0, MINDIF + ctr - max)) elseif p[3] - p[2] == -1 then d = math.min(d, math.max(0, max - ctr - MINDIF)) end - + if p[3] == 1 then d = math.min(d, math.max(0, MAXOUT - max)) end - + -- Update output values output.min = min + p[1] * d output.offset = ctr + p[2] * d output.max = max + p[3] * d - + -- Write back data model.setOutput(channel.iChannel - 1, output) end - + function interval.onEvent(event, touchState) if event == EVT_VIRTUAL_ENTER and not channel.editing then channel.editing = true @@ -586,7 +586,7 @@ function widget.refresh(event, touchState) init() return end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/wing2.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/wing2.lua index 1526c149..18b4e776 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/wing2.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/wing2.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui = libGUI.newGUI() local colors = libGUI.colors diff --git a/sdcard/c480x272/WIDGETS/SoarETX/1/wing4.lua b/sdcard/c480x272/WIDGETS/SoarETX/1/wing4.lua index db35e1a0..29f1853f 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/1/wing4.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/1/wing4.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui = libGUI.newGUI() local colors = libGUI.colors diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/ailctr.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/ailctr.lua index 510ee947..1bc43694 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/ailctr.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/ailctr.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors @@ -84,9 +84,9 @@ end local function adjust(slider) -- Compensate for possible negative differential local dif = model.getGlobalVariable(GV_DIF, 0) - local difComp = 100.0 / math.max(10.0, math.min(100.0, 100.0 + dif)) + local difComp = 100.0 / math.max(10.0, math.min(100.0, 100.0 + dif)) -- Calculate aileron travel from current airbrake travel - local ail = math.min(2 * slider.value, 2 * (100 - slider.value) * difComp) + local ail = math.min(2 * slider.value, 2 * (100 - slider.value) * difComp) model.setGlobalVariable(GV_AIL, 0, ail) model.setGlobalVariable(GV_BRK, 0, slider.value) end @@ -96,7 +96,7 @@ end do function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, title, bit32.bor(DBLSIZE, colors.primary2)) @@ -107,12 +107,12 @@ do local brk = 2 * model.getGlobalVariable(GV_BRK, 0) local brkDeg = 0.45 * brk local dif = 0.01 * model.getGlobalVariable(GV_DIF, 0) - + lcd.drawPie(CTR_X, CTR_Y, R2, 90 - ailDeg * math.min(1, 1 + dif), 91 + brkDeg, colors.primary2) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90 + ailDeg * math.min(1, 1 - dif), 90 + brkDeg, COLOR_THEME_SECONDARY2) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90, 90 + ailDeg * math.min(1, 1 - dif), COLOR_BLEND) lcd.drawAnnulus(CTR_X, CTR_Y, R1, R2, 90 - ailDeg * math.min(1, 1 + dif), 90, COLOR_THEME_SECONDARY1) - + lcd.drawArc(CTR_X, CTR_Y, R2, 90 - ailDeg * math.min(1, 1 + dif), 90 + brkDeg, colors.primary3) drawRadian(90 - ailDeg * math.min(1, 1 + dif)) drawRadian(90) @@ -132,7 +132,7 @@ do lcd.drawText(CTR_X + R1, CTR_Y, "brake ", RIGHT + SMLSIZE + colors.primary1) lcd.drawText(CTR_X + R2, CTR_Y - SML_H, " max.", SMLSIZE + colors.primary1) lcd.drawText(CTR_X + R2, CTR_Y, " reflex", SMLSIZE + colors.primary1) - + local txt = "Use the slider to adjust the flaperons to the position of maximum reflex.\n\n" .. "Notice that camber can only move the flaperons down from this position." lcd.drawTextLines(MARGIN, TOP, CTR_X - 2 * MARGIN, LCD_H - TOP - MARGIN, txt, colors.primary1) @@ -178,6 +178,6 @@ function widget.refresh(event, touchState) setStickySwitch(LS_CTR, true) slider.value = model.getGlobalVariable(GV_BRK, 0) end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/brkcrv.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/brkcrv.lua index 8b0b2d65..9c9b74d0 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/brkcrv.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/brkcrv.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = nil local colors = libGUI.colors @@ -203,7 +203,7 @@ end -- Setup GUI function widget.background() if (gui ~= nil) then stepOff() - gui = nil + gui = nil end end -- background() diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/f3J.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/f3J.lua index 0872dad9..92349f19 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/f3J.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/f3J.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = DBLSIZE local colors = libGUI.colors diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/f3k.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/f3k.lua index 7592147a..b96ca2ae 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/f3k.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/f3k.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = DBLSIZE local colors = libGUI.colors local activeGUI @@ -40,7 +40,7 @@ local RGT = LCD_W - 18 local TOP = 50 local BOTTOM = LCD_H - 30 local LINE = 60 -local LINE2 = 28 +local LINE2 = 28 local HEIGHT = 42 local HEIGHT2 = 18 local BUTTON_W = 86 @@ -82,7 +82,7 @@ local state -- Current program state local scores = { } -- List of saved scores local taskWindow = 0 -- Task window duration (zero counts up) local launches = -1 -- Number of launches allowed, -1 for unlimited -local taskScores = 0 -- Number of scores in task +local taskScores = 0 -- Number of scores in task local finalScores = false -- Task scores are final local targetType = 0 -- 1. Huge ladder, 2. Poker, 3. "1234", 4. Big ladder, Else: constant time local scoreType -- 1. Best, 2. Last, 3. Make time @@ -111,7 +111,7 @@ local SCORE_FILE = "/LOGS/JF F3K Scores.csv" -- Handle transitions between program states local function GotoState(newState) state = newState - + -- Stop blinking screenTask.timer0.blink = false @@ -126,13 +126,13 @@ local function GotoState(newState) setStickySwitch(LS_FLT_TMR, false) screenTask.labelTimer0.title = "Target:" screenTask.locked = true - + elseif state == STATE_FLYING then setStickySwitch(LS_WIN_TMR, true) setStickySwitch(LS_FLT_TMR, true) screenTask.labelTimer0.title = "Flight:" screenTask.locked = true - + if model.getTimer(0).start > 0 then -- Report the target time playDuration(model.getTimer(0).start, 0) @@ -140,35 +140,35 @@ local function GotoState(newState) -- ... or beep playTone(1760, 100, PLAY_NOW) end - + elseif state == STATE_COMMITTED then -- Call launch height if getLogicalSwitchValue(LS_ALT) then playNumber(getValue("Alt+"), UNIT_METERS) end - - if launches > 0 then + + if launches > 0 then launches = launches - 1 end - + lastChange = 0 - + elseif state == STATE_FINISHED then playTone(880, 1000, 0) end - + -- Configure "button3" screenTask.button3.disabled = false if state <= STATE_PAUSE then - screenTask.button3.title = "Start" + screenTask.button3.title = "Start" elseif state == STATE_WINDOW then screenTask.button3.title = "Pause" elseif state >= STATE_COMMITTED then screenTask.button3.title = "Zero" else - screenTask.button3.disabled = true + screenTask.button3.disabled = true end - + -- Configure info text label if state == STATE_PAUSE then screenTask.labelInfo.title = string.format("Total: %i sec.", totalScore) @@ -188,18 +188,18 @@ end -- GotoState() -- Function for setting up a task local function SetupTask(taskName, taskData) screenTask.title = taskName - + taskWindow = taskData[1] launches = taskData[2] taskScores = taskData[3] finalScores = taskData[4] targetType = taskData[5] scoreType = taskData[6] - screenTask.buttonQR.value = taskData[7] + screenTask.buttonQR.value = taskData[7] scores = { } totalScore = 0 pokerCalled = false - + -- Setup scores for i = 1, N_LINES do if i > taskScores then @@ -210,7 +210,7 @@ local function SetupTask(taskName, taskData) screenTask.scores[i].hidden = false end end - + -- A few extra counts in 1234 if targetType == 3 then counts = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30, 45, 65, 70, 75, 125, 130, 135, 185, 190, 195} @@ -236,7 +236,7 @@ local function RecordBest(scores, newScore) if newScore > scores[i] then j = i end i = i + 1 end - + if j == 0 then j = i end -- New score is smallest; end of the list end @@ -298,7 +298,7 @@ local function Score() end - totalScore = 0 + totalScore = 0 for i = 1, #scores do totalScore = totalScore + math.min(MaxScore(i), scores[i]) end @@ -307,7 +307,7 @@ end -- Score() -- Reset altimeter local function ResetAlt() for i = 0, 31 do - if model.getSensor(i).name == "Alt" then + if model.getSensor(i).name == "Alt" then model.resetSensor(i) break end @@ -370,11 +370,11 @@ end -- Best1234Target(..) -- Get called time from user in Poker local function PokerCall() local dial - + -- Find dials for setting target time in Poker and height ceilings etc. for input = 0, 31 do local tbl = model.getInput(input, 0) - + if tbl and tbl.name == "Dial" then dial = tbl.source end @@ -393,24 +393,24 @@ local function PokerCall() t2 = tblStep[i + 1][1] end local dt = tblStep[i][2] - + local result = t1 + dt * math.floor(x * (t2 - t1) /dt) - + if scoreType == 3 then result = math.min(winTimer - 1, result) end - + if math.abs(input - lastInput) >= 20 then lastInput = input lastChange = getTime() end - + if state == STATE_COMMITTED and lastChange > 0 and getTime() - lastChange > 100 then playTone(3000, 100, PLAY_NOW) playDuration(result) lastChange = 0 end - + return result end -- PokerCall() @@ -435,11 +435,11 @@ local function TargetTime() return MaxScore(#scores + 1) end end -- TargetTime() - + -- Initialize variables before flight local function InitializeFlight() local targetTime = TargetTime() - + -- Get ready to count down countIndex = #counts while countIndex > 1 and counts[countIndex] >= targetTime do @@ -468,7 +468,7 @@ function widget.background() ResetAlt() end - + -- Call altitude every 10 sec. if getLogicalSwitchValue(LS_ALT10) and now > nextCall then playNumber(getValue("Alt"), UNIT_METERS) @@ -478,11 +478,11 @@ function widget.background() if state <= STATE_READY and state ~= STATE_FINISHED then InitializeFlight() end - + flightTimer = model.getTimer(0).value flightTime = math.abs(model.getTimer(0).start - flightTimer) winTimer = model.getTimer(1).value - + if state < STATE_WINDOW then if state == STATE_IDLE then -- Set window timer @@ -515,7 +515,7 @@ function widget.background() -- Play tone to warn that timer is NOT running playTone(1760, 200, 0, PLAY_NOW) end - + elseif state == STATE_READY then if launchReleased then GotoState(STATE_FLYING) @@ -533,12 +533,12 @@ function widget.background() elseif flightTimer > 0 and math.ceil(flightTimer / 60) < math.ceil(prevFt / 60) then playDuration(flightTimer, 0) end - + -- Blink when flight ttimer is negative if flightTimer < 0 then screenTask.timer0.blink = true end - + if state == STATE_FLYING then -- Within 10 sec. "grace period", cancel the flight if launchPulled then @@ -549,7 +549,7 @@ function widget.background() if flightTime >= 10 then GotoState(STATE_COMMITTED) end - + elseif launchPulled then -- Report the time after flight is done if model.getTimer(0).start == 0 then @@ -557,7 +557,7 @@ function widget.background() end Score() - + -- Change state if (finalScores and #scores == taskScores) or launches == 0 or (taskWindow > 0 and winTimer <= 0) then GotoState(STATE_FINISHED) @@ -568,7 +568,7 @@ function widget.background() end end end - + prevWt = winTimer prevFt = flightTimer end @@ -622,12 +622,12 @@ function libGUI.widgetRefresh() local COL2 = COL1 + 30 local COL3 = COL1 + 125 local RGT = COL1 + 400 - + -- Draw scores x = 5 local y = 0 local dy = widget.zone.h / N_LINES - + for i = 1, taskScores do lcd.drawText(COL1, y, string.format("%i.", i), colors.primary1 + DBLSIZE) if i > #scores then @@ -641,14 +641,14 @@ function libGUI.widgetRefresh() -- Draw timers local blink = 0 local y = 4 - + local tmr = model.getTimer(0).value - if tmr < 0 and state == STATE_COMMITTED then + if tmr < 0 and state == STATE_COMMITTED then blink = BLINK end lcd.drawText(COL3, y + 10, screenTask.labelTimer0.title, colors.primary1 + DBLSIZE) - lcd.drawTimer(RGT, y, tmr, colors.primary1 + blink + XXLSIZE + RIGHT) + lcd.drawTimer(RGT, y, tmr, colors.primary1 + blink + XXLSIZE + RIGHT) y = y + 2 * dy tmr = model.getTimer(1).value lcd.drawText(COL3, y + 10, "Task:", colors.primary1 + DBLSIZE) @@ -660,13 +660,13 @@ end -- widgetRefresh() local function SetupScreen(gui, title, pop) gui.title = title local x1 - + if pop then x1 = LCD_W - 80 else x1 = LCD_W - 50 end - + function gui.fullScreenRefresh() local color lcd.clear(COLOR_THEME_SECONDARY3) @@ -678,17 +678,17 @@ local function SetupScreen(gui, title, pop) -- Date local now = getDateTime() local str = string.format("%02i:%02i", now.hour, now.min) - lcd.drawText(x1, 6, str, RIGHT + MIDSIZE + colors.primary2) + lcd.drawText(x1, 6, str, RIGHT + MIDSIZE + colors.primary2) if soarGlobals.battery == 0 then color = COLOR_THEME_DISABLED else color = colors.primary2 end - + str = string.format("%1.1fV", soarGlobals.battery) lcd.drawText(x1 - 65, 6, str, RIGHT + MIDSIZE + color) - + -- Draw trims local p = { { LCD_W - 191, LCD_H - 14, 177, 8 }, @@ -696,7 +696,7 @@ local function SetupScreen(gui, title, pop) { LCD_W - 14, 68, 8, 177 }, { 7, 68, 8, 177 }, } - + for i = 1, 4 do local q = p[i] local value = getValue(trimSources[i]) / 10.24 @@ -708,21 +708,21 @@ local function SetupScreen(gui, title, pop) x = q[1] + q[3] / 2 y = q[2] + q[4] * (100 - value) / 200 end - + lcd.drawFilledRectangle(q[1], q[2], q[3], q[4], COLOR_THEME_SECONDARY1) lcd.drawFilledRectangle(x - 9, y - 6, 18, 15, colors.primary1) lcd.drawFilledRectangle(x - 10, y - 7, 18, 15, colors.focus) lcd.drawNumber(x, y, value, SMLSIZE + VCENTER + CENTER + colors.primary2) end - + -- Flight mode - lcd.drawText(LCD_W / 2, LCD_H - LINE2, select(2, getFlightMode()), MIDSIZE + CENTER + COLOR_THEME_SECONDARY1) + lcd.drawText(LCD_W / 2, LCD_H - LINE2, select(2, getFlightMode()), MIDSIZE + CENTER + COLOR_THEME_SECONDARY1) end -- fullScreenRefresh() - + -- Return button if pop then gui.buttonRet = gui.custom({ }, LCD_W - 74, 6, 28, 28) - + function gui.buttonRet.draw(focused) local color @@ -733,14 +733,14 @@ local function SetupScreen(gui, title, pop) color = COLOR_THEME_DISABLED gui.buttonRet.disabled = true end - + lcd.drawRectangle(LCD_W - 74, 6, 28, 28, color) lcd.drawFilledRectangle(LCD_W - 61, 12, 3, 18, color) for i = 0, 3 do lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 50 - i, 20, SOLID, color) lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 70 + i, 20, SOLID, color) end - + if focused then gui.buttonRet.drawFocus() end @@ -752,7 +752,7 @@ local function SetupScreen(gui, title, pop) end end end - + -- Minimize button local buttonMin = gui.custom({ }, LCD_W - 34, 6, 28, 28) @@ -764,7 +764,7 @@ local function SetupScreen(gui, title, pop) buttonMin.drawFocus() end end - + function buttonMin.onEvent(event) if event == EVT_VIRTUAL_ENTER then lcd.exitFullScreen() @@ -780,7 +780,7 @@ local function SetupScreen(gui, title, pop) end end gui.setEventHandler(EVT_VIRTUAL_EXIT, HandleEXIT) - + return gui end -- SetupScreen @@ -791,7 +791,7 @@ do local ROW = 65 x = 40 y = 60 - + SetupScreen(menuMain, "SoarETX F3K") -- Generate callbacks with closure for calling submenus @@ -806,7 +806,7 @@ do menuMain.button(x, y, WIDTH, HEIGHT, "Practice", MakePush(menuPractice)) y = y + ROW menuMain.button(x, y, WIDTH, HEIGHT, "Scores", MakePush(menuScores)) - + activeGUI = menuMain end @@ -831,7 +831,7 @@ do -- Setup F3K tasks menu "L. One flight only", "M. Huge Ladder" } - + -- {win, launches, scores, final, tgtType, scoType, QR } local taskData = { { 420, -1, 1, false, 300, 2, false }, -- A. Last flight @@ -862,7 +862,7 @@ end do -- Setup practice tasks menu SetupScreen(menuPractice, "Practice Tasks", true) - + local tasks = { "Just Fly!", "Quick Relaunch!", @@ -875,7 +875,7 @@ do -- Setup practice tasks menu { 0, -1, 5, false, 2, 2, true }, -- QR { 600, 2, 2, true, 5, 2, false } -- Deuces } - + -- Call back function running when a menu item is selected local function callBack(menu) SetupTask(tasks[menu.selected], taskData[menu.selected]) @@ -888,13 +888,13 @@ end do -- Setup score keeper screen for F3K and Practice tasks SetupScreen(screenTask, "", true) - + -- Restore default task and dismiss task screen - function screenTask.dismiss() + function screenTask.dismiss() SetupTask("Just Fly!", { 0, -1, 5, false, 0, 2, false }) PopGUI() end - + -- Return button shows prompt to save scores instead of popping right away function screenTask.buttonRet.onEvent(event) if event == EVT_VIRTUAL_ENTER then @@ -905,17 +905,17 @@ do -- Setup score keeper screen for F3K and Practice tasks end end end - + -- Add score times local y = TOP local dy = select(2, lcd.sizeText("", libGUI.flags)) - + screenTask.scoreLabels = { } screenTask.scores = { } for i = 1, N_LINES do screenTask.scoreLabels[i] = screenTask.label(LEFT, y, 20, HEIGHT, string.format("%i.", i)) - + local s = screenTask.timer(LEFT + 40, y, 60, HEIGHT, 0, nil) s.disabled = true s.value = "- - -" @@ -924,35 +924,35 @@ do -- Setup score keeper screen for F3K and Practice tasks -- Modify timer's draw function to insert score value local draw = s.draw function s.draw(idx) - if i > #scores then + if i > #scores then screenTask.scores[i].value = "- - -" else screenTask.scores[i].value = scores[i] end - + draw(idx) end y = y + dy end - + -- Add center buttons local y = TOP screenTask.buttonQR = screenTask.toggleButton(COL2, y, BUTTON_W, HEIGHT, "QR", false, nil) y = y + LINE screenTask.buttonEoW = screenTask.toggleButton(COL2, y, BUTTON_W, HEIGHT, "EoW", true, nil) - + local function callBack(button) if state <= STATE_PAUSE then GotoState(STATE_WINDOW) - + elseif state == STATE_WINDOW then GotoState(STATE_PAUSE) - + elseif state >= STATE_COMMITTED then -- Record a zero score! flightTime = 0 Score() - + -- Change state if winTimer <= 0 or (finalScores and #scores == taskScores) or launches == 0 then GotoState(STATE_FINISHED) @@ -962,26 +962,26 @@ do -- Setup score keeper screen for F3K and Practice tasks end end end - + y = y + LINE screenTask.button3 = screenTask.button(COL2, y, BUTTON_W, HEIGHT, "Start", callBack) - + -- Info text label screenTask.labelInfo = screenTask.label(RGT - 250, BOT_ROW, 250, HEIGHT, "", libGUI.flags + RIGHT) - + -- Add timers y = TOP screenTask.labelTimer0 = screenTask.label(RGT - 160, y, 50, HEIGHT2, "Target:", MIDSIZE) y = y + LINE2 screenTask.timer0 = screenTask.timer(RGT - 160, y, 160, HEIGHT, 0, nil, XXLSIZE + RIGHT) screenTask.timer0.disabled = true - + y = y + LINE screenTask.label(RGT - 160, y, 50, HEIGHT2, "Task:", MIDSIZE) y = y + LINE2 local tmr = screenTask.timer(RGT - 160, y, 160, HEIGHT, 1, nil, XXLSIZE + RIGHT) tmr.disabled = true - + -- Short press EXIT handler must prompt to save scores local function HandleEXIT(event, touchState) if CanPopGUI() then @@ -1001,7 +1001,7 @@ do -- Prompt asking to save scores and exit task window local RGT = x0 + PROMPT_W - PROMPT_M local TOP = y0 + PROMPT_M local BOTTOM = y0 + PROMPT_H - PROMPT_M - + function promptSaveScores.fullScreenRefresh() lcd.drawFilledRectangle(x0, y0, PROMPT_W, PROMPT_H, colors.primary2) lcd.drawRectangle(x0, y0, PROMPT_W, PROMPT_H, colors.primary1, 3) @@ -1015,21 +1015,21 @@ do -- Prompt asking to save scores and exit task window if scoreFile then io.write(scoreFile, string.format("%s,%s", model.getInfo().name, screenTask.title)) - local now = getDateTime() + local now = getDateTime() io.write(scoreFile, string.format(",%04i-%02i-%02i", now.year, now.mon, now.day)) - io.write(scoreFile, string.format(",%02i:%02i", now.hour, now.min)) + io.write(scoreFile, string.format(",%02i:%02i", now.hour, now.min)) io.write(scoreFile, string.format(",s,%i", taskScores)) io.write(scoreFile, string.format(",%i", totalScore)) - + for i = 1, #scores do io.write(scoreFile, string.format(",%i", scores[i])) end - + io.write(scoreFile, "\n") io.close(scoreFile) end end - + -- Dismiss prompt and return to menu screenTask.dismissPrompt() screenTask.dismiss() @@ -1047,7 +1047,7 @@ do -- Setup score browser screen local scoreFile -- File handle local pos -- Read position in file local firstRecordTouch -- First record at the start of touch slide - + -- Read a line of a log file local function ReadLine(scoreFile, pos) if scoreFile and pos then @@ -1061,7 +1061,7 @@ do -- Setup score browser screen return pos, str end end - + -- No "\n" was found; return nothing return 0, "" end -- ReadLine() @@ -1074,7 +1074,7 @@ do -- Setup score browser screen for field in string.gmatch(str, "[^,]+") do i = i + 1 - + if i == 1 then record.planeName = field elseif i == 2 then @@ -1093,33 +1093,33 @@ do -- Setup score browser screen record.scores[#record.scores + 1] = tonumber(field) end end - + if record.totalScore then records[#records + 1] = record end end -- ReadLineData() - + local function DrawRecord(i, r) local top = 40 + i * RECORD_H local left = 200 local w = (LCD_W - left - 10) / 3 local record = records[r] - + if not record then return end - + if r % 2 == 0 then lcd.drawFilledRectangle(0, top, LCD_W, RECORD_H, COLOR_THEME_SECONDARY2) end - + lcd.drawText(10, top + 6, record.taskName, BOLD) lcd.drawText(10, top + 24, record.dateStr .. " " .. record.timeStr, SMLSIZE) lcd.drawText(10, top + 36, record.planeName, SMLSIZE) - + local x = left local y = top + 6 - + for j = 1, math.min(5, record.taskScores) do lcd.drawText(x, y, j .. ".") @@ -1130,7 +1130,7 @@ do -- Setup score browser screen else lcd.drawText(x + 18, y, record.scores[j] .. record.unitStr) end - + if j == 3 then x = left y = top + 30 @@ -1138,7 +1138,7 @@ do -- Setup score browser screen x = x + w end end - + lcd.drawText(left + 2 * w, top + 30, "Total: " .. record.totalScore .. record.unitStr) end -- DrawRecord @@ -1152,7 +1152,7 @@ do -- Setup score browser screen end lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, "Score Card", bit32.bor(DBLSIZE, colors.primary2)) @@ -1160,25 +1160,25 @@ do -- Setup score browser screen -- Date local now = getDateTime() local str = string.format("%02i:%02i", now.hour, now.min) - lcd.drawText(LCD_W - 80, 6, str, RIGHT + MIDSIZE + colors.primary2) + lcd.drawText(LCD_W - 80, 6, str, RIGHT + MIDSIZE + colors.primary2) if soarGlobals.battery == 0 then color = COLOR_THEME_DISABLED else color = colors.primary2 end - + str = string.format("%1.1fV", soarGlobals.battery) lcd.drawText(LCD_W - 140, 6, str, RIGHT + MIDSIZE + color) - + -- Return button lcd.drawFilledRectangle(LCD_W - 74, 6, 28, 28, COLOR_THEME_SECONDARY1) lcd.drawRectangle(LCD_W - 74, 6, 28, 28, colors.primary2) - + for i = -1, 1 do lcd.drawLine(LCD_W - 60 + i, 12, LCD_W - 60 + i, 30, SOLID, colors.primary2) end - + for i = 0, 3 do lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 50 - i, 20, SOLID, colors.primary2) lcd.drawLine(LCD_W - 60 , 10 + i, LCD_W - 70 + i, 20, SOLID, colors.primary2) @@ -1188,17 +1188,17 @@ do -- Setup score browser screen lcd.drawFilledRectangle(LCD_W - 34, 6, 28, 28, COLOR_THEME_SECONDARY1) lcd.drawRectangle(LCD_W - 34, 6, 28, 28, colors.primary2) lcd.drawFilledRectangle(LCD_W - 30, 19, 20, 3, colors.primary2) - + if event ~= EVT_TOUCH_SLIDE then firstRecordTouch = nil end - + if event == EVT_VIRTUAL_EXIT then firstRecord = nil return PopGUI() elseif event == EVT_TOUCH_TAP then local x, y = touchState.x, touchState.y - + if 6 <= y and y <= 34 then if LCD_W - 74 <= x and x <= LCD_W - 40 then firstRecord = nil @@ -1226,9 +1226,9 @@ do -- Setup score browser screen local r = i + firstRecord DrawRecord(i, r) end - + else -- Read score records - lcd.drawText(LCD_W / 2, LCD_H / 2, "Reading scores ...", VCENTER + CENTER + DBLSIZE + colors.primary1) + lcd.drawText(LCD_W / 2, LCD_H / 2, "Reading scores ...", VCENTER + CENTER + DBLSIZE + colors.primary1) if not scoreFile then scoreFile = io.open(SCORE_FILE, "r") @@ -1237,7 +1237,7 @@ do -- Setup score browser screen records = { } end end - + if scoreFile then for i = 1, 10 do local str @@ -1247,11 +1247,11 @@ do -- Setup score browser screen io.close(scoreFile) scoreFile = nil firstRecord = math.max(1, #records - 3) - + if #records == 0 then firstRecord = nil end - + break end end diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/f5J.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/f5J.lua index d2e5f60d..246e1cae 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/f5J.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/f5J.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = DBLSIZE local colors = libGUI.colors @@ -291,7 +291,7 @@ function widget.background() if motorOn then GotoState(STATE_MOTOR) -- Reset MotorTime Call and Alt Window Time - prevMt = flightTimer.value + prevMt = flightTimer.value offTime = 0 end elseif state == STATE_MOTOR then diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/graph.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/graph.lua index 97bdc70e..95225a0b 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/graph.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/graph.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local colors = libGUI.colors local title = "Graph" diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/mixes.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/mixes.lua index 46498d46..c774b9ce 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/mixes.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/mixes.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui = libGUI.newGUI() local colors = libGUI.colors @@ -84,40 +84,40 @@ local mixes = mixes_F3K do -- Extract Model Type from parametes - modelType = widget.options.Type + modelType = widget.options.Type if modelType == "F3K" or modelType == "F3K_TRAD" then mixes = mixes_F3K elseif modelType == "F3K_FH" then mixes = mixes_F3K_FH elseif modelType == "F3K_RE" then - mixes = mixes_F3K_RE + mixes = mixes_F3K_RE elseif modelType == "F3J" or modelType == "F5J" then - mixes = mixes_FxJ + mixes = mixes_FxJ else - mixes = mixes_FXY + mixes = mixes_FXY modelType = "F??" end function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) - + -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(10, 2, title.." "..modelType, bit32.bor(DBLSIZE, colors.primary2)) - + -- Fligh mode local fmIdx, fmStr = getFlightMode() lcd.drawText(LCD_W - HEADER, HEADER / 2, "FM" .. fmIdx .. ":" .. fmStr, RIGHT + VCENTER + MIDSIZE + colors.primary2) - + -- Line stripes for i = 1, 3, 2 do lcd.drawFilledRectangle(0, HEADER + LINE * i, LCD_W, LINE, COLOR_THEME_SECONDARY2) end - + local bottom = HEADER + 4 * LINE lcd.drawLine(LCD_W2, HEADER, LCD_W2, bottom, SOLID, colors.primary1) - + -- Help text local txt = "Some variables can be adjusted individually for each flight mode.\n" .. "Therefore, select the flight mode for which you want to adjust.\n" .. @@ -145,7 +145,7 @@ do -- Grid for items local x, y = MARGIN, HEADER + 2 - + local function move() if x == MARGIN then x = x + LCD_W2 @@ -154,11 +154,11 @@ do y = y + LINE end end - + -- Add label and number element for a GV local function addGV(label, gv, min, max) gui.label(x, y, W1, HEIGHT, label) - + local function changeGV(delta, number) local value = number.value + delta value = math.max(value, min) @@ -166,16 +166,16 @@ do model.setGlobalVariable(gv, fm, value) return value end - + local number = gui.number(x + W1, y, W2, HEIGHT, 0, changeGV, RIGHT + libGUI.flags) - + function number.update() number.value = model.getGlobalVariable(gv, fm) end - + move() end - + -- ADD GVs for i, mix in ipairs(mixes) do addGV(mix[1], mix[2], mix[3], mix[4]) @@ -191,7 +191,7 @@ do soarGlobals.setParameter(soarGlobals.batteryParameter, value - 100) return value end - + local batP = soarGlobals.getParameter(soarGlobals.batteryParameter) gui.number(x + W1, y, W2, HEIGHT, batP + 100, changeBattery, RIGHT + PREC1 + libGUI.flags) end -- Setup GUI @@ -206,8 +206,8 @@ function widget.refresh(event, touchState) lcd.drawText(widget.zone.w / 2, widget.zone.h / 2, title, CENTER + VCENTER + MIDSIZE + colors.primary2) return end - + fm = getFlightMode() - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/outputs.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/outputs.lua index a0738913..b9d7674d 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/outputs.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/outputs.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui local colors = libGUI.colors @@ -75,12 +75,12 @@ do local PROMPT_H = 172 warningPrompt.x = (LCD_W - PROMPT_W) / 2 warningPrompt.y = (LCD_H - PROMPT_H) / 2 - + function warningPrompt.fullScreenRefresh() local txt = "Please disable the motor!\n\n" .. "Sudden spikes may occur when channels are moved.\n\n" .. "Press ENTER to proceed." - + warningPrompt.drawFilledRectangle(0, 0, PROMPT_W, HEADER, COLOR_THEME_SECONDARY1) warningPrompt.drawFilledRectangle(0, HEADER, PROMPT_W, PROMPT_H - HEADER, libGUI.colors.primary2) warningPrompt.drawRectangle(0, 0, PROMPT_W, PROMPT_H, libGUI.colors.primary1, 2) @@ -125,7 +125,7 @@ do } editPrompt.x = (LCD_W - PROMPT_W) / 2 editPrompt.y = (LCD_H - PROMPT_H) / 2 - + function editPrompt.fullScreenRefresh() if not editPrompt.editing then editPoints = 0 @@ -138,7 +138,7 @@ do editPrompt.drawFilledRectangle(0, HEADER, PROMPT_W, PROMPT_H - HEADER, libGUI.colors.primary2) editPrompt.drawRectangle(0, 0, PROMPT_W, PROMPT_H, libGUI.colors.primary1, 2) editPrompt.drawText(MARGIN, HEADER / 2, "Select what to edit:", DBLSIZE + VCENTER + libGUI.colors.primary2) - + local y = HEADER + MARGIN + h / 2 for i = 1, 5 do @@ -162,7 +162,7 @@ do editPoints = menu.selected gui.dismissPrompt() end -- onMenu(...) - + editPrompt.menu(MARGIN, HEADER + MARGIN, MENU_W, MENU_H, menuItems, onMenu) end -- Prompt for selecting what to edit @@ -171,13 +171,13 @@ local function MoveOutput(direction, channel) local m = { } -- Channel indices m[1] = channel.iChannel -- Channel to move m[2] = m[1] + direction -- Neighbouring channel to swap - + -- Are we at then end? if m[2] < 1 or m[2] > N then playTone(3000, 100, 0, PLAY_NOW) return end - + local outputs = { } -- List of output tables local mixes = { } -- List of lists of mixer tables @@ -191,7 +191,7 @@ local function MoveOutput(direction, channel) mixes[i][j] = model.getMix(m[i] - 1, j - 1) end end - + -- Write back swapped data for i = 1, 2 do model.setOutput(m[i] - 1, outputs[3 - i]) @@ -223,7 +223,7 @@ local function MoveOutput(direction, channel) mixes[j].source = m[1] + CHAN_BASE end end - + -- Do we have to write back data? if dirty then -- Delete existing mixer lines @@ -262,7 +262,7 @@ local function init() -- Top bar lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) lcd.drawText(MARGIN, HEADER / 2 - 2, "Configure Outputs", DBLSIZE + VCENTER + colors.primary2) - + -- Row background for i = 0, 6 do local y = HEADER + i * ROW @@ -272,7 +272,7 @@ local function init() lcd.drawFilledRectangle(0, y, LCD_W, ROW, COLOR_THEME_SECONDARY3) end end - + -- Adjust scroll for channels if focusNamed > 0 then if focusNamed < firstLine then @@ -282,7 +282,7 @@ local function init() end end focusNamed = 0 - + -- Draw vertical reference lines for i = -6, 6 do local x = CTR - i * MAXOUT / (SCALE * 6) + 2 @@ -313,10 +313,10 @@ local function init() local HEIGHT = ROW - 8 local iNamed = 0 channels = { } - + for iChannel = 1, N do local output = model.getOutput(iChannel - 1) - + if output and output.name ~= "" then local channel = gui.gui(2, LCD_H, LCD_W - 4, ROW - 4) local d0 @@ -356,8 +356,8 @@ local function init() end -- onEvent(...) -- Custom element for changing output channel (and moving all mixer lines etc.) - local nbrChannel = channel.custom({ }, 2, 2, 30, HEIGHT) - + local nbrChannel = channel.custom({ }, 2, 2, 30, HEIGHT) + function nbrChannel.draw(focused) local fg = libGUI.colors.primary1 if focused then @@ -391,13 +391,13 @@ local function init() channel.editing = true end end -- onEvent(...) - + -- Label for channel name local lblName = channel.label(32, 2, 140, HEIGHT, ". " .. channel.output.name) - + -- Custom element to invert output direction local revert = channel.custom({ }, 172, 2, 30, HEIGHT) - + function revert.draw(focused) local y = HEIGHT / 2 + 3 if channel.output.revert == 1 then @@ -413,7 +413,7 @@ local function init() channel.drawLine(x, y, x - 8, y + 8, SOLID, colors.primary1) end end - + function revert.onEvent(event, touchState) if event == EVT_VIRTUAL_ENTER then channel.output.revert = 1 - channel.output.revert @@ -435,14 +435,14 @@ local function init() local y = HEIGHT / 2 + 2 local yLbl = y - 12 - select(2, lcd.sizeText("", flags)) local iScroll = 0 - + function interval.draw(focused) local output = channel.output local p = { 0, 0, 0 } local colorBar = libGUI.colors.primary3 local colorDot = libGUI.colors.primary2 local colorDotBorder = libGUI.colors.primary3 - + x = { CTR + output.min / SCALE, CTR + output.offset / SCALE, @@ -457,7 +457,7 @@ local function init() channel.drawNumber(x[3], yLbl, 0.1 * output.max, flags) colorBar = libGUI.colors.primary1 colorDot = libGUI.colors.edit - p = activePoints(editPoints) + p = activePoints(editPoints) else interval.drawFocus() end @@ -477,21 +477,21 @@ local function init() -- Draw position indicators local outX = getValue(CHAN_BASE + channel.iChannel) if outX >= 0 then - outX = output.offset + math.min(outX, 1024) * (output.max - output.offset) / 1024 + outX = output.offset + math.min(outX, 1024) * (output.max - output.offset) / 1024 else - outX = output.offset + math.max(outX, -1024) * (output.offset - output.min) / 1024 + outX = output.offset + math.max(outX, -1024) * (output.offset - output.min) / 1024 end outX = CTR + outX / SCALE channel.drawFilledTriangle(outX, y - 3, outX - 3, y - 9, outX + 3, y - 9, colorBar) channel.drawLine(outX, y - 2, outX, y + 2, SOLID, colorBar) channel.drawFilledTriangle(outX, y + 3, outX - 3, y + 9, outX + 3, y + 9, colorBar) end -- draw(...) - + local RR = 14 ^ 2 - + local function ptCovers(p, q) local ap = activePoints(editPoints) - + for i = 1, 3 do if ap[i] ~= 0 and (x[i] - p) ^ 2 + (y - q) ^ 2 <= RR then return i @@ -499,46 +499,46 @@ local function init() end return 0 end -- ptCovers(...) - + local function adjustPoints(d) local output = channel.output local p = activePoints(editPoints) local min = output.min local ctr = output.offset local max = output.max - + -- Check limits if p[1] == -1 then d = math.min(d, math.max(0, MAXOUT + min)) elseif p[1] == 1 then d = math.max(d, math.min(0, -(MAXOUT + min))) end - + if p[2] - p[1] == 1 then d = math.max(d, math.min(0, MINDIF + min - ctr)) elseif p[2] - p[1] == -1 then d = math.min(d, math.max(0, ctr - min - MINDIF)) end - + if p[3] - p[2] == 1 then d = math.max(d, math.min(0, MINDIF + ctr - max)) elseif p[3] - p[2] == -1 then d = math.min(d, math.max(0, max - ctr - MINDIF)) end - + if p[3] == 1 then d = math.min(d, math.max(0, MAXOUT - max)) end - + -- Update output values output.min = min + p[1] * d output.offset = ctr + p[2] * d output.max = max + p[3] * d - + -- Write back data model.setOutput(channel.iChannel - 1, output) end - + function interval.onEvent(event, touchState) if event == EVT_VIRTUAL_ENTER and not channel.editing then channel.editing = true @@ -586,7 +586,7 @@ function widget.refresh(event, touchState) init() return end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/switch.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/switch.lua index 4bcc0524..cb6e33ec 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/switch.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/switch.lua @@ -21,7 +21,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = 0 local gui local colors = libGUI.colors @@ -37,53 +37,53 @@ local WIDTH = 60 local COL2 = LCD_W - MARGIN - WIDTH -- List of 1. Text label 2. logical switch -local items_F3K = { -- For R3K - { "Allow vario and voice reporting of altitude", 0 }, +local items_F3K = { -- For R3K + { "Allow vario and voice reporting of altitude", 0 }, { "Variometer sound", 1 }, - { "Speed flight mode", 2 }, - { "Float flight mode", 3 }, - { "Report remaining window time every 10 sec.", 4 }, - { "Report current altitude every 10 sec.", 5 }, - { "Launch mode and flight timer control", 6 }, + { "Speed flight mode", 2 }, + { "Float flight mode", 3 }, + { "Report remaining window time every 10 sec.", 4 }, + { "Report current altitude every 10 sec.", 5 }, + { "Launch mode and flight timer control", 6 }, { "Data logging (when flight timer is running)", 7 } } -local items_F3K_RE = { -- For R3K_RE - { "Allow vario and voice reporting of altitude", 0 }, +local items_F3K_RE = { -- For R3K_RE + { "Allow vario and voice reporting of altitude", 0 }, { "Variometer sound", 1 }, - { "Speed flight mode", 2 }, - { "Report remaining window time every 10 sec.", 4 }, - { "Report current altitude every 10 sec.", 5 }, - { "Launch mode and flight timer control", 6 }, + { "Speed flight mode", 2 }, + { "Report remaining window time every 10 sec.", 4 }, + { "Report current altitude every 10 sec.", 5 }, + { "Launch mode and flight timer control", 6 }, { "Data logging (when flight timer is running)", 7 } } -local items_F5K = { - { "Allow vario and voice reporting of altitude", 0 }, +local items_F5K = { + { "Allow vario and voice reporting of altitude", 0 }, { "Variometer sound", 1 }, - { "Speed flight mode", 2 }, - { "Float flight mode", 3 }, + { "Speed flight mode", 2 }, + { "Float flight mode", 3 }, { "Motor ARM ON/OFF", 4}, - { "Report remaining window time every 10 sec.", 5 }, - { "Report current altitude every 10 sec.", 6 }, - { "Launch mode and flight timer control", 7 }, + { "Report remaining window time every 10 sec.", 5 }, + { "Report current altitude every 10 sec.", 6 }, + { "Launch mode and flight timer control", 7 }, { "Data logging (when flight timer is running)", 8 } } local items_FxJ = { - { "Allow vario and voice reporting of altitude", 0 }, - { "Variometer sound", 1 }, - { "Speed flight mode", 2 }, - { "Float flight mode", 3 }, - { "Report remaining window time every 10 sec.", 6 }, - { "Report current altitude every 10 sec.", 7 }, - { "Launch mode (Motor Arm) and flight timer control", 4 }, - { "Start/Stop timer and Motor", 8 }, - { "Data logging (when flight timer is running)", 9 } + { "Allow vario and voice reporting of altitude", 0 }, + { "Variometer sound", 1 }, + { "Speed flight mode", 2 }, + { "Float flight mode", 3 }, + { "Report remaining window time every 10 sec.", 6 }, + { "Report current altitude every 10 sec.", 7 }, + { "Launch mode (Motor Arm) and flight timer control", 4 }, + { "Start/Stop timer and Motor", 8 }, + { "Data logging (when flight timer is running)", 9 } } local items_FXY = { - { "Allow vario and voice reporting of altitude", 0 } + { "Allow vario and voice reporting of altitude", 0 } } local items = items_FXY @@ -130,11 +130,11 @@ local function init() -- Build the list of drop downs local y = HEADER + 2 local w1 = COL2 - MARGIN - + -- Build lists of physical switch position indices and names local swIndices = { } local swNames = { } - + for swIdx, swName in switches() do if string.find(swName,"^!?S[A-H][+-]?") then i = #swIndices + 1 @@ -149,9 +149,9 @@ local function init() lsTbl.v1 = swIdx model.setLogicalSwitch(dropDown.ls, lsTbl) end - + -- Extract Model Type from parametes - modelType = widget.options.Type + modelType = widget.options.Type if modelType == "F3K" or modelType == "F3K_FH" or modelType == "F3K_TRAD" then items = items_F3K @@ -162,27 +162,27 @@ local function init() HEIGHT = 20 LINE = 25 elseif modelType == "F3J" or modelType == "F5J" then - items = items_FxJ + items = items_FxJ HEIGHT = 20 -- Make it smaller to fit extra line LINE = 25 else - items = items_FXY + items = items_FXY modelType = "F??" end for i, item in ipairs(items) do gui.label(MARGIN, y, w1, HEIGHT, item[1]) - + local swIdx = model.getLogicalSwitch(item[2]).v1 local selected = 0 - + for i, idx in ipairs(swIndices) do if swIdx == idx then selected = i break end end - + if selected == 0 then -- Oops, no switch matching current value in LS! gui.label(COL2, y, WIDTH, HEIGHT, "???", CENTER + BOLD) @@ -210,6 +210,6 @@ function widget.refresh(event, touchState) init() return end - + gui.run(event, touchState) end -- refresh(...) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/wing2.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/wing2.lua index 78a72828..05fae03d 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/wing2.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/wing2.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui = nil local colors = libGUI.colors @@ -268,7 +268,7 @@ local function setup_gui() gui = libGUI.newGUI() -- Extract Model Type from parametes - modelType = widget.options.Type + modelType = widget.options.Type if modelType == "F3K" or modelType == "F3K_TRAD" then LS_STEP = 10 -- Logical Switch 10 diff --git a/sdcard/c480x272/WIDGETS/SoarETX/2/wing4.lua b/sdcard/c480x272/WIDGETS/SoarETX/2/wing4.lua index b7b112a4..910fbd82 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/2/wing4.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/2/wing4.lua @@ -20,7 +20,7 @@ --------------------------------------------------------------------------- local widget, soarGlobals = ... -local libGUI = loadGUI() +local libGUI = soarGlobals.libGUI libGUI.flags = MIDSIZE local gui = nil local colors = libGUI.colors @@ -43,7 +43,7 @@ local BUTTON_Y = (LCD_H + TOP + HEIGHT - BUTTON_H) / 2 -- Other constants local INP_STEP = getFieldInfo("input8").id -- Step input local LS_STEP = nil -- Set this LS to apply step input and adjust -local GV_ADJUST = nil +local GV_ADJUST = nil local N = 5 -- Number of curve points local MAX_Y = 1500 -- Max output value local MINDIF = 100 -- Minimum difference between lower, center and upper values @@ -287,16 +287,16 @@ local function setup_gui() gui = libGUI.newGUI() -- Extract Model Type from parametes - modelType = widget.options.Type + modelType = widget.options.Type if modelType == "F3K_FH" then LS_STEP = 10 -- L11 elseif modelType == "F3J" or modelType == "F5J" then - GV_ADJUST = 7 -- GV8:Adj + GV_ADJUST = 7 -- GV8:Adj else LS_STEP = nil modelType = "F??" end - + function gui.fullScreenRefresh() lcd.clear(COLOR_THEME_SECONDARY3) diff --git a/sdcard/c480x272/WIDGETS/SoarETX/libgui.lua b/sdcard/c480x272/WIDGETS/SoarETX/libgui.lua new file mode 100644 index 00000000..32228014 --- /dev/null +++ b/sdcard/c480x272/WIDGETS/SoarETX/libgui.lua @@ -0,0 +1,1127 @@ +--------------------------------------------------------------------------- +-- The dynamically loadable part of the shared Lua GUI library. -- +-- -- +-- Author: Jesper Frickmann -- +-- Version: 1.0.0 Date: 2021-12-20 -- +-- Version: 1.0.1 Date: 2022-05-05 -- +-- Version: 1.0.2 Date: 2022-11-20 -- +-- Version: 1.0.2 Date: 2023-07 -- +-- Version: 1.0.3 Date: 2023-12 -- +-- -- +-- Copyright (C) EdgeTX -- +-- -- +-- License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html -- +-- -- +-- This program is free software; you can redistribute it and/or modify -- +-- it under the terms of the GNU General Public License version 2 as -- +-- published by the Free Software Foundation. -- +-- -- +-- This program is distributed in the hope that it will be useful -- +-- but WITHOUT ANY WARRANTY; without even the implied warranty of -- +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- +-- GNU General Public License for more details. -- +--------------------------------------------------------------------------- + +local app_ver = "1.0.3" + +local M = { } + +-- Radius of slider dot +M.SLIDER_DOT_RADIUS = 10 + +-- better font size names +M.FONT_SIZES = { + FONT_38 = XXLSIZE, -- 38px + FONT_16 = DBLSIZE, -- 16px + FONT_12 = MIDSIZE, -- 12px + FONT_8 = 0, -- Default 8px + FONT_6 = SMLSIZE, -- 6px +} + +-- Default flags and colors, can be changed by client +M.flags = 0 +M.colors = { + primary1 = COLOR_THEME_PRIMARY1, + primary2 = COLOR_THEME_PRIMARY2, -- button background / topbar text + primary3 = COLOR_THEME_PRIMARY3, -- button text + secondary1 = COLOR_THEME_SECONDARY1, -- topbar background + secondary2 = COLOR_THEME_SECONDARY2, -- button border + secondary3 = COLOR_THEME_SECONDARY3, -- screen background + focus = COLOR_THEME_FOCUS, + edit = COLOR_THEME_EDIT, + active = COLOR_THEME_ACTIVE, +} + +function M.getVer() + return app_ver +end + +-- Return true if the first arg matches any of the following args +function M.match(x, ...) + for i, y in ipairs({ ... }) do + if x == y then + return true + end + end + return false +end + + +-- Create a new GUI object with interactive screen elements +function M.newGUI() + local gui = { + x = 0, + y = 0, + editable = true, + } + + local _ = {} -- internal members + _.handles = { } + _.elements = { } + _.focus = 1 + _.scrolling = false + _.lastEvent = 0 + + + function _.lcdSizeTextFixed(txt, font_size) + local ts_w, ts_h = lcd.sizeText(txt, font_size) + + local v_offset = 0 + if font_size == M.FONT_SIZES.FONT_38 then + v_offset = -11 + elseif font_size == M.FONT_SIZES.FONT_16 then + v_offset = -5 + elseif font_size == M.FONT_SIZES.FONT_12 then + v_offset = -4 + elseif font_size == M.FONT_SIZES.FONT_8 then + v_offset = -3 + elseif font_size == M.FONT_SIZES.FONT_6 then + v_offset = 0 + end + return ts_w, ts_h +2*v_offset, v_offset + end + + -- Translate coordinates for sub-GUIs + function gui.translate(x, y) + if gui.parent then + x, y = gui.parent.translate(x, y) + end + return gui.x + x, gui.y + y + end + + -- Replace lcd functions to translate by gui offset + function gui.drawCircle(x, y, r, flags) + x, y = gui.translate(x, y) + lcd.drawCircle(x, y, r, flags) + end + + function gui.drawFilledCircle(x, y, r, flags) + x, y = gui.translate(x, y) + lcd.drawFilledCircle(x, y, r, flags) + end + + function gui.drawLine(x1, y1, x2, y2, pattern, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + lcd.drawLine(x1, y1, x2, y2, pattern, flags) + end + + function gui.drawRectangle(x, y, w, h, flags, t) + x, y = gui.translate(x, y) + lcd.drawRectangle(x, y, w, h, flags, t) + end + + function gui.drawFilledRectangle(x, y, w, h, flags, opacity) + x, y = gui.translate(x, y) + lcd.drawFilledRectangle(x, y, w, h, flags, opacity) + end + + function gui.drawTriangle(x1, y1, x2, y2, x3, y3, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + x3, y3 = gui.translate(x3, y3) + lcd.drawTriangle(x1, y1, x2, y2, x3, y3, flags) + end + + function gui.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + x3, y3 = gui.translate(x3, y3) + lcd.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) + end + + function gui.drawText(x, y, text, flags, inversColor) + x, y = gui.translate(x, y) + local ts_w, ts_h, v_offset = _.lcdSizeTextFixed(text, M.FONT_SIZES.FONT_8) + lcd.drawText(x, y + v_offset, text, flags, inversColor) + --lcd.drawText(x, y, text, flags, inversColor) + end + + function gui.drawTextLines(x, y, w, h, text, flags) + x, y = gui.translate(x, y) + lcd.drawTextLines(x, y, w, h, text, flags) + end + + function gui.drawNumber(x, y, value, flags, inversColor) + x, y = gui.translate(x, y) + lcd.drawNumber(x, y, value, flags, inversColor) + end + + function gui.drawTimer(x, y, value, flags, inversColor) + x, y = gui.translate(x, y) + lcd.drawTimer(x, y, value, flags, inversColor) + end + + -- The default callBack + function _.doNothing() + end + + -- The default onChangeValue + function _.onChangeDefault(delta, self) + return self.value + delta + end + + -- Adjust text according to horizontal alignment + function _.align(x, w, flags) + if bit32.band(flags, RIGHT) == RIGHT then + return x + w + elseif bit32.band(flags, CENTER) == CENTER then + return x + w / 2 + else + return x + end + end -- align(...) + + -- Draw border around focused elements + local function drawFocus(x, y, w, h, color) + -- Not necessary if there is only one element... + if #_.elements == 1 then + return + end + color = color or M.colors.focus + gui.drawRectangle(x - 2, y - 2, w + 4, h + 4, color, 2) + end -- drawFocus(...) + + -- Move focus to another element + local function moveFocus(delta) + local count = 0 -- Prevent infinite loop + repeat + _.focus = _.focus + delta + if _.focus > #_.elements then + _.focus = 1 + elseif _.focus < 1 then + _.focus = #_.elements + end + count = count + 1 + until not (_.elements[_.focus].disabled or _.elements[_.focus].hidden) or count > #_.elements + end -- moveFocus(...) + + -- Moved the focused element + function gui.moveFocused(delta) + if delta > 0 then + delta = 1 + elseif delta < 0 then + delta = -1 + end + local idx = _.focus + delta + if idx >= 1 and idx <= #_.elements then + _.elements[_.focus], _.elements[idx] = _.elements[idx], _.elements[_.focus] + _.focus = idx + end + end + + -- Add an element and return it to the client + local function addElement(element, x, y, w, h) + if not element.covers then + function element.covers(p, q) + return (x <= p and p <= x + w and y <= q and q <= y + h) + end + end + + _.elements[#_.elements+1] = element + return element + end -- addElement(...) + + -- Add temporary BLINK or INVERS flags + local function getFlags(element) + local flags = element.flags + if element.blink then flags = bit32.bor(flags or 0, BLINK) end + if element.invers then flags = bit32.bor(flags or 0, INVERS) end + return flags + end + + -- Set an event handler + function gui.setEventHandler(event, f) + _.handles[event] = f + end + + -- Show prompt + function gui.showPrompt(prompt) + M.prompt = prompt + end + + -- Dismiss prompt + function gui.dismissPrompt() + M.prompt = nil + end + + ----------------------------------------------------------------------------------------------- + + -- Run an event cycle + function gui.run(event, touchState) + gui.draw(false) + if event ~= nil then + gui.onEvent(event, touchState) + end + _.lastEvent = event + end -- run(...) + + ----------------------------------------------------------------------------------------------- + + function gui.draw(focused) + if gui.fullScreenRefresh then + gui.fullScreenRefresh() + end + if focused then + if gui.parent.editing then + drawFocus(0, 0, gui.w, gui.h, M.colors.edit) + else + drawFocus(0, 0, gui.w, gui.h) + end + end + local guiFocus = not gui.parent or (focused and gui.parent.editing) + for idx, element in ipairs(_.elements) do + -- Clients may provide an update function for elements + if element.onUpdate then -- New name for method + element.onUpdate(element) + elseif element.update then -- For backward compatibility + element.update(element) + end + if not element.hidden then + element.draw(_.focus == idx and guiFocus) + end + end + end -- draw() + + ----------------------------------------------------------------------------------------------- + + function gui.onEvent(event, touchState) + -- Make sure that focused element is active + if (_.elements[_.focus].disabled or _.elements[_.focus].hidden) then + moveFocus(1) + return + end + -- Is there an active prompt? + if M.prompt and not M.showingPrompt then + M.showingPrompt = true + M.prompt.run(event, touchState) + M.showingPrompt = false + return + end + + if event == 0 then + return + end + + if gui.parent and not gui.parent.editing then + if event == EVT_VIRTUAL_ENTER then + gui.parent.editing = true + end + return + end + + -- non-zero event; process it + -- Translate touch coordinates if offset + if touchState then + touchState.x = touchState.x - gui.x + touchState.y = touchState.y - gui.y + if touchState.startX then + touchState.startX = touchState.startX - gui.x + touchState.startY = touchState.startY - gui.y + end + -- "Un-convert" ENTER to TAP + if event == EVT_VIRTUAL_ENTER then + event = EVT_TOUCH_TAP + end + end + + -- ETX 2.8 rc 4 bug fix + if _.scrolling and event == EVT_VIRTUAL_ENTER_LONG then + return + end + -- If we put a finger down on a menu item and immediately slide, then we can scroll + if event == EVT_TOUCH_SLIDE then + if not _.scrolling then + return + end + else + _.scrolling = false + end + + -- "Pre-processing" of touch events to simplify subsequent handling and support scrolling etc. + if event == EVT_TOUCH_FIRST then + if _.elements[_.focus].covers(touchState.x, touchState.y) then + _.scrolling = true + else + if gui.editing then + return + else + -- Did we touch another element? + for idx, element in ipairs(_.elements) do + if not (element.disabled or element.hidden) and element.covers(touchState.x, touchState.y) then + _.focus = idx + _.scrolling = true + end + end + end + end + elseif event == EVT_TOUCH_TAP or (event == EVT_TOUCH_BREAK and _.lastEvent == EVT_TOUCH_FIRST) then + if _.elements[_.focus].covers(touchState.x, touchState.y) then + -- Convert TAP on focused element to ENTER + event = EVT_VIRTUAL_ENTER + elseif gui.editing then + -- Convert a TAP off the element being edited to EXIT + event = EVT_VIRTUAL_EXIT + end + end + + if gui.editing then -- Send the event directly to the element being edited + _.elements[_.focus].onEvent(event, touchState) + elseif event == EVT_VIRTUAL_NEXT then -- Move focus + moveFocus(1) + elseif event == EVT_VIRTUAL_PREV then + moveFocus(-1) + elseif event == EVT_VIRTUAL_EXIT and gui.parent then + gui.parent.editing = false + else + if _.handles[event] then + -- Is it being handled? Handler can modify event + event = _.handles[event](event, touchState) + -- If handler returned false or nil, then we are done + if not event then + return + end + end + _.elements[_.focus].onEvent(event, touchState) + end + end -- onEvent(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a text label + function gui.label(x, y, w, h, title, flags) + local self = { + title = title, + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), + disabled = true, + hidden= false, + } + + function self.draw(focused) + local flags = getFlags(self) + gui.drawText(_.align(x, w, flags), y + h / 2, self.title, flags) + end + + -- We should not ever onEvent, but just in case... + function self.onEvent(event, touchState) + self.disabled = true + moveFocus(1) + end + + function self.covers(p, q) + return false + end + + addElement(self, x, y, w, h) + return self + end -- label(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a text label lines + function gui.labelLines(x, y, w, h, title, flags) + local self = { + title = title, + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), + disabled = true, + hidden= false, + } + + function self.draw(focused) + local flags = getFlags(self) + gui.drawTextLines(_.align(x, w, flags), y , w, h, self.title, flags) + end + + -- We should not ever onEvent, but just in case... + function self.onEvent(event, touchState) + self.disabled = true + moveFocus(1) + end + + function self.covers(p, q) + return false + end + + addElement(self, x, y, w, h) + return self + end -- label(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a button to trigger a function + function gui.button(x, y, w, h, title, callBack, flags) + local self = { + title = title, + callBack = callBack or _.doNothing, + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), + disabled = false, + hidden= false + } + + function self.draw(focused) + if focused then + drawFocus(x, y, w, h) + end + + gui.drawFilledRectangle(x, y, w, h, M.colors.primary2) + gui.drawRectangle(x, y, w, h, M.colors.secondary2) + gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(M.colors.secondary1, self.flags)) + + if self.disabled then + gui.drawFilledRectangle(x, y, w, h, GREY, 7) + end + end + + function self.onEvent(event, touchState) + if event == EVT_VIRTUAL_ENTER then + return self.callBack(self) + end + end + + addElement(self, x, y, w, h) + return self + end -- button(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a toggle button that turns on/off. callBack gets true/false + function gui.toggleButton(x, y, w, h, title, value, callBack, flags) + local self = { + title = title, + value = value, + callBack = callBack or _.doNothing, + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), + disabled = false, + hidden= false + } + + function self.draw(focused) + local fg = M.colors.primary3 + local bg = M.colors.primary2 + local border = M.colors.secondary2 + + if self.value then + fg = M.colors.secondary3 + bg = M.colors.secondary1 + border = M.colors.focus + end + + if focused then + drawFocus(x, y, w, h, border) + end + + gui.drawFilledRectangle(x, y, w, h, bg) + gui.drawRectangle(x, y, w, h, border) + gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(fg, self.flags)) + + if self.disabled then + gui.drawFilledRectangle(x, y, w, h, GREY, 7) + end + end + + function self.onEvent(event, touchState) + if event == EVT_VIRTUAL_ENTER then + self.value = not self.value + return self.callBack(self) + end + end + + addElement(self, x, y, w, h) + return self + end -- toggleButton(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a number that can be edited + function gui.number(x, y, w, h, value, onChangeValue, flags, min, max) + local self = { + value = value, + onChangeValue = onChangeValue or _.onChangeDefault, + flags = bit32.bor(flags or M.flags, VCENTER), + editable = true, + disabled = false, + hidden= false, + min_val = min or 0, + max_val = max or 100, + } + + local d0 + + function self.draw(focused) + local flags = getFlags(self) + local fg = M.colors.primary1 + + if focused then + drawFocus(x, y, w, h) + + if gui.editing then + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) + end + end + if type(self.value) == "string" then + gui.drawText(_.align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) + else + gui.drawNumber(_.align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) + end + end + + function self.onEvent(event, touchState) + if gui.editing then + if event == EVT_VIRTUAL_ENTER then + gui.editing = false + elseif event == EVT_VIRTUAL_EXIT then + self.value = value + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + if self.value < self.max_val then + self.value = self.onChangeValue(1, self) + end + elseif event == EVT_VIRTUAL_DEC then + if self.value > self.min_val then + self.value = self.onChangeValue(-1, self) + end + elseif event == EVT_TOUCH_FIRST then + d0 = 0 + elseif event == EVT_TOUCH_SLIDE then + local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) + if d ~= d0 then + self.value = self.onChangeValue(d - d0, self) + d0 = d + end + end + elseif event == EVT_VIRTUAL_ENTER then + value = self.value + gui.editing = true + end + end -- onEvent(...) + + addElement(self, x, y, w, h) + return self + end -- number(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a display of current time on timer[tmr] + -- Set timer.value to show a different value + function gui.timer(x, y, w, h, tmr, onChangeValue, flags) + local self = { + tmr = tmr, + onChangeValue = onChangeValue or _.onChangeDefault, + flags = bit32.bor(flags or M.flags, VCENTER), + disabled = false, + hidden= false, + editable = true + } + local value + local d0 + + function self.draw(focused) + local flags = getFlags(self) + local fg = M.colors.primary1 + -- self.value overrides the timer value + local value = self.value or model.getTimer(self.tmr).value + + if focused then + drawFocus(x, y, w, h) + + if gui.editing then + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) + end + end + if type(value) == "string" then + gui.drawText(_.align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) + else + gui.drawTimer(_.align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) + end + end + + function self.onEvent(event, touchState) + if gui.editing then + if event == EVT_VIRTUAL_ENTER then + if not value and self.tmr then + local tblTmr = model.getTimer(self.tmr) + tblTmr.value = self.value + model.setTimer(self.tmr, tblTmr) + self.value = nil + end + gui.editing = false + elseif event == EVT_VIRTUAL_EXIT then + self.value = value + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = self.onChangeValue(1, self) + elseif event == EVT_VIRTUAL_DEC then + self.value = self.onChangeValue(-1, self) + elseif event == EVT_TOUCH_FIRST then + d0 = 0 + elseif event == EVT_TOUCH_SLIDE then + local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) + if d ~= d0 then + self.value = self.onChangeValue(d - d0, self) + d0 = d + end + end + elseif event == EVT_VIRTUAL_ENTER then + if self.value then + value = self.value + elseif self.tmr then + self.value = model.getTimer(self.tmr).value + value = nil + end + gui.editing = true + end + end -- onEvent(...) + + addElement(self, x, y, w, h) + return self + end -- timer(...) + + ----------------------------------------------------------------------------------------------- + + function gui.menu(x, y, w, h, items, callBack, flags) + local self = { + items = items or { "No items!" }, + flags = bit32.bor(flags or M.flags, VCENTER), + disabled = false, + hidden= false, + editable = true, + selected = 1 + } + local selected = 1 + local firstVisible = 1 + local firstVisibleScrolling + local moving = 0 + local lh = select(2, lcd.sizeText("", self.flags)) + local visibleCount = math.floor(h / lh) + local killEvt + + callBack = callBack or _.doNothing + + local function setFirstVisible(v) + firstVisible = v + firstVisible = math.max(1, firstVisible) + firstVisible = math.min(#self.items - visibleCount + 1, firstVisible) + end + + local function adjustScroll() + if selected >= firstVisible + visibleCount then + firstVisible = selected - visibleCount + 1 + elseif selected < firstVisible then + firstVisible = selected + end + end + + function self.draw(focused) + local flags = getFlags(self) + local visibleCount = math.min(visibleCount, #self.items) + local sel + local bgColor + + if focused and gui.editing then + bgColor = M.colors.edit + else + selected = self.selected + bgColor = M.colors.focus + end + + for i = 0, visibleCount - 1 do + local j = firstVisible + i + local y = y + i * lh + + if j == selected then + gui.drawFilledRectangle(x, y, w, lh, bgColor) + gui.drawText(_.align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary2, flags)) + else + gui.drawText(_.align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary1, flags)) + end + end + + if focused then + drawFocus(x, y, w, h) + end + end -- draw() + + function self.onEvent(event, touchState) + local visibleCount = math.min(visibleCount, #self.items) + + if moving ~= 0 then + if M.match(event, EVT_TOUCH_FIRST, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + moving = 0 + event = 0 + else + setFirstVisible(firstVisible + moving) + end + end + + if event ~= 0 then + -- This hack is needed because killEvents does not seem to work + if killEvt then + killEvt = false + if event == EVT_VIRTUAL_ENTER then + event = 0 + end + end + + -- If we touch it, then start editing immediately + if touchState then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + if _.scrolling then + if touchState.swipeUp then + moving = 1 + elseif touchState.swipeDown then + moving = -1 + elseif touchState.startX then + setFirstVisible(firstVisibleScrolling + math.floor((touchState.startY - touchState.y) / lh + 0.5)) + end + end + else + _.scrolling = false + + if event == EVT_TOUCH_FIRST then + _.scrolling = true + firstVisibleScrolling = firstVisible + elseif M.match(event, EVT_VIRTUAL_NEXT, EVT_VIRTUAL_PREV) then + if event == EVT_VIRTUAL_NEXT then + selected = math.min(#self.items, selected + 1) + elseif event == EVT_VIRTUAL_PREV then + selected = math.max(1, selected - 1) + end + adjustScroll() + elseif event == EVT_VIRTUAL_ENTER then + if gui.editing then + if touchState then + selected = firstVisible + math.floor((touchState.y - y) / lh) + end + + gui.editing = false + self.selected = selected + callBack(self) + else + gui.editing = true + selected = self.selected + adjustScroll() + end + elseif event == EVT_VIRTUAL_EXIT then + gui.editing = false + end + end + end + end -- onEvent(...) + + addElement(self, x, y, w, h) + return self + end -- menu(...) + + ----------------------------------------------------------------------------------------------- + + function gui.dropDown(x, y, w, h, items, selected, callBack, flags) + callBack = callBack or _.doNothing + flags = flags or M.flags + + local self + local showingMenu + local drawingMenu + local dropDown = M.newGUI() + local lh = select(2, lcd.sizeText("", flags)) + local height = math.min(0.75 * LCD_H, #items * lh) + local top = (LCD_H - height) / 2 + + dropDown.x = gui.translate(0, 0) + top = math.min(top, y) + top = math.max(top, y + h - height) + + local function dismissMenu() + showingMenu = false + gui.dismissPrompt() + end + + function dropDown.fullScreenRefresh() + if not dropDown.editing then + dismissMenu() + return + end + dropDown.drawFilledRectangle(x, top, w, height, M.colors.primary2) + dropDown.drawRectangle(x - 2, top - 2, w + 4, height + 4, M.colors.primary1, 2) + drawingMenu = true + end + + local function onMenu(menu) + dismissMenu() + callBack(self) + end + + self = dropDown.menu(x, top, w, height, items, onMenu, flags) + self.selected = selected + local drawMenu = self.draw + + function self.draw(focused) + if drawingMenu then + drawingMenu = false + drawMenu(focused) + else + local flags = bit32.bor(VCENTER, M.colors.primary1, getFlags(self)) + + if focused then + drawFocus(x, y, w, h) + end + gui.drawText(_.align(x, w, flags), y + h / 2, self.items[self.selected], flags) + local dd = lh / 2 + local yy = y + (h - dd) / 2 + local xx = (x-5) + w - 1.15 * dd + gui.drawTriangle(x-5 + w, yy, (x-5 + w + xx) / 2, yy + dd, xx, yy, M.colors.primary1) + end + end + + local onMenu = self.onEvent + + function self.onEvent(event, touchState) + if showingMenu then + onMenu(event, touchState) + elseif event == EVT_VIRTUAL_ENTER then + -- Show drop down and let it take over while active + showingMenu = true + dropDown.onEvent(event) + gui.showPrompt(dropDown) + else + end + end + + local coverMenu = self.covers + + function self.covers(p, q) + if showingMenu then + return coverMenu(p, q) + else + return (x <= p and p <= x + w and y <= q and q <= y + h) + end + end + + addElement(self, x, y, w, h) + return self + end -- dropDown(...) + + ----------------------------------------------------------------------------------------------- + + function gui.horizontalSlider(x, y, w, value, min, max, delta, callBack) + local self = { + value = value, + min = min, + max = max, + delta = delta, + callBack = callBack or _.doNothing, + disabled = false, + hidden= false, + editable = true + } + + function self.draw(focused) + local xdot = x + w * (self.value - self.min) / (self.max - self.min) + + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 + + if focused then + colorDotBorder = M.colors.active + if gui.editing or _.scrolling then + colorBar = M.colors.primary1 + colorDot = M.colors.edit + end + end + + gui.drawFilledRectangle(x, y - 2, w, 5, colorBar) + gui.drawFilledCircle(xdot, y, M.SLIDER_DOT_RADIUS, colorDot) + for i = -1, 1 do + gui.drawCircle(xdot, y, M.SLIDER_DOT_RADIUS + i, colorDotBorder) + end + end + + function self.onEvent(event, touchState) + local v0 = self.value + + if gui.editing then + if M.match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = math.min(self.max, self.value + self.delta) + elseif event == EVT_VIRTUAL_DEC then + self.value = math.max(self.min, self.value - self.delta) + end + elseif event == EVT_VIRTUAL_ENTER then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + local value = self.min + (self.max - self.min) * (touchState.x - x) / w + value = math.min(self.max, value) + value = math.max(self.min, value) + self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) + end + + if v0 ~= self.value then + self.callBack(self) + end + end + + function self.covers(p, q) + local xdot = x + w * (self.value - self.min) / (self.max - self.min) + return ((p - xdot) ^ 2 + (q - y) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) + end + + addElement(self) + return self + end -- horizontalSlider(...) + + ----------------------------------------------------------------------------------------------- + + function gui.verticalSlider(x, y, h, value, min, max, delta, callBack) + local self = { + value = value, + min = min, + max = max, + delta = delta, + callBack = callBack or _.doNothing, + disabled = false, + hidden= false, + editable = true + } + + function self.draw(focused) + local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) + + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 + + if focused then + colorDotBorder = M.colors.active + if gui.editing or _.scrolling then + colorBar = M.colors.primary1 + colorDot = M.colors.edit + end + end + + gui.drawFilledRectangle(x - 2, y, 5, h, colorBar) + gui.drawFilledCircle(x, ydot, M.SLIDER_DOT_RADIUS, colorDot) + for i = -1, 1 do + gui.drawCircle(x, ydot, M.SLIDER_DOT_RADIUS + i, colorDotBorder) + end + end + + function self.onEvent(event, touchState) + local v0 = self.value + + if gui.editing then + if M.match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = math.min(self.max, self.value + self.delta) + elseif event == EVT_VIRTUAL_DEC then + self.value = math.max(self.min, self.value - self.delta) + end + elseif event == EVT_VIRTUAL_ENTER then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + local value = self.max - (self.max - self.min) * (touchState.y - y) / h + value = math.min(self.max, value) + value = math.max(self.min, value) + self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) + end + + if v0 ~= self.value then + self.callBack(self) + end + end + + function self.covers(p, q) + local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) + return ((p - x) ^ 2 + (q - ydot) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) + end + + addElement(self) + return self + end -- verticalSlider(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a custom element + function gui.custom(self, x, y, w, h) + self.gui = gui + self.lib = M + + function self.drawFocus(color) + drawFocus(self.x or x, self.y or y, self.w or w, self.h or h, color) + end + + -- Must be implemented by the client + if not self.draw then + function self.draw(focused) + gui.drawText(x, y, "draw(focused) missing") + if focused then + drawFocus(x, y, w, h) + end + end + end + + -- Must be implemented by the client + if not self.onEvent then + function self.onEvent() + playTone(200, 200, 0, PLAY_NOW) + end + end + + addElement(self, x, y, w, h) + return self + end + + ----------------------------------------------------------------------------------------------- + + -- Create a nested gui + function gui.gui(x, y, w, h) + local self = M.newGUI() + self.parent = gui + self.editing = false + self.x, self.y, self.w, self.h = x, y, w, h + + function self.covers(p, q) + return (self.x <= p and p <= self.x + self.w and self.y <= q and q <= self.y + self.h) + end + + addElement(self, x, y, w, h) + return self + end + + + ----------------------------------------------------------------------------------------------- + return gui + +end -- gui(...) + +return M diff --git a/sdcard/c480x272/WIDGETS/SoarETX/main.lua b/sdcard/c480x272/WIDGETS/SoarETX/main.lua index fe3c5332..3ec6370f 100644 --- a/sdcard/c480x272/WIDGETS/SoarETX/main.lua +++ b/sdcard/c480x272/WIDGETS/SoarETX/main.lua @@ -21,9 +21,9 @@ --------------------------------------------------------------------------- local options = { - { "Version", VALUE, 1, 1, 99 }, + { "Version", VALUE, 1, 1, 2 }, { "FileName", STRING, "" }, - { "Type", STRING, "" } + { "Type", STRING, "" } -- F3K|F3K_TRAD|F3K_FH|F3K_RE|F3J|F5J } local soarGlobals @@ -34,21 +34,21 @@ local rxBatNxtCheck = 0 function rxBatCheck() local now = getTime() - + if now < rxBatNxtCheck then return end - + rxBatNxtCheck = now + 100 - + local rxBatSrc = getFieldInfo("Cels") if not rxBatSrc then rxBatSrc = getFieldInfo("RxBt") end if not rxBatSrc then rxBatSrc = getFieldInfo("A1") end if not rxBatSrc then rxBatSrc = getFieldInfo("A2") end - + if rxBatSrc then soarGlobals.battery = getValue(rxBatSrc.id) - + if type(soarGlobals.battery) == "table" then for i = 2, #soarGlobals.battery do soarGlobals.battery[1] = math.min(soarGlobals.battery[1], soarGlobals.battery[i]) @@ -85,7 +85,7 @@ local function GetCurve(crvIndex) if #oldTbl.y == N then -- Normal Behaviour return oldTbl end - + -- Work arround the bug of GetCurve in some versions (2.8.3) of ETX if #oldTbl.y == N - 1 then local newTbl = { } @@ -110,10 +110,13 @@ local function init() batteryParameter = 1, getCurve = GetCurve } + soarGlobals.libGUI = loadScript("/WIDGETS/SoarETX/libgui.lua")() + local gui = soarGlobals.libGUI.newGUI() + -- Functions to handle persistent model parameters stored in curve 32 local parameterCurve = GetCurve(31) - + if not parameterCurve then error("Curve #32 is missing! It is used to store persistent model parameters for Lua.") end @@ -133,7 +136,7 @@ local function create(zone, options) if not soarGlobals then init() end - + local widget = { zone = zone, options = options @@ -145,7 +148,7 @@ end local function update(widget, options) if options.Version ~= widget.options.Version or options.FileName ~= widget.options.FileName then local zone = widget.zone - + -- Erase all fields in widget local keys = { } for key in pairs(widget) do @@ -154,7 +157,7 @@ local function update(widget, options) for i, key in ipairs(keys) do widget[key] = nil end - + widget.zone = zone widget.options = options Load(widget) @@ -178,10 +181,10 @@ local function background(widget) end return { - name = "SoarETX", - create = create, - refresh = refresh, - options = options, - update = update, + name = "SoarETX", + create = create, + refresh = refresh, + options = options, + update = update, background = background -} \ No newline at end of file +} diff --git a/sdcard/c480x320/WIDGETS/LibGUI/libgui.lua b/sdcard/c480x320/WIDGETS/LibGUI/libgui.lua index 99b6266e..2706e8a2 100644 --- a/sdcard/c480x320/WIDGETS/LibGUI/libgui.lua +++ b/sdcard/c480x320/WIDGETS/LibGUI/libgui.lua @@ -2,8 +2,11 @@ -- The dynamically loadable part of the shared Lua GUI library. -- -- -- -- Author: Jesper Frickmann -- --- Date: 2022-05-05 -- --- Version: 1.0.1 -- +-- Version: 1.0.0 Date: 2021-12-20 -- +-- Version: 1.0.1 Date: 2022-05-05 -- +-- Version: 1.0.2 Date: 2022-11-20 -- +-- Version: 1.0.2 Date: 2023-07 -- +-- Version: 1.0.3 Date: 2023-12 -- -- -- -- Copyright (C) EdgeTX -- -- -- @@ -19,965 +22,1101 @@ -- GNU General Public License for more details. -- --------------------------------------------------------------------------- -local lib = { } +local app_ver = "1.0.3" + +local M = { } -- Radius of slider dot -local SLIDER_DOT_RADIUS = 10 +M.SLIDER_DOT_RADIUS = 10 + +-- better font size names +M.FONT_SIZES = { + FONT_38 = XXLSIZE, -- 38px + FONT_16 = DBLSIZE, -- 16px + FONT_12 = MIDSIZE, -- 12px + FONT_8 = 0, -- Default 8px + FONT_6 = SMLSIZE, -- 6px +} -- Default flags and colors, can be changed by client -lib.flags = 0 -lib.colors = { - primary1 = COLOR_THEME_PRIMARY1, - primary2 = COLOR_THEME_PRIMARY2, - primary3 = COLOR_THEME_PRIMARY3, - focus = COLOR_THEME_FOCUS, - edit = COLOR_THEME_EDIT, - active = COLOR_THEME_ACTIVE, +M.flags = 0 +M.colors = { + primary1 = COLOR_THEME_PRIMARY1, + primary2 = COLOR_THEME_PRIMARY2, + primary3 = COLOR_THEME_PRIMARY3, + focus = COLOR_THEME_FOCUS, + edit = COLOR_THEME_EDIT, + active = COLOR_THEME_ACTIVE, } +function M.getVer() + return app_ver +end + -- Return true if the first arg matches any of the following args -local function match(x, ...) - for i, y in ipairs({...}) do - if x == y then - return true +function M.match(x, ...) + for i, y in ipairs({ ... }) do + if x == y then + return true + end end - end - return false + return false end -lib.match = match -- Create a new GUI object with interactive screen elements -function lib.newGUI() - local gui = { - x = 0, - y = 0, - editable = true - } - - local handles = { } - local elements = { } - local focus = 1 - local scrolling = false - local lastEvent = 0 - - -- Translate coordinates for sub-GUIs - function gui.translate(x, y) - if gui.parent then - x, y = gui.parent.translate(x, y) +function M.newGUI() + local gui = { + x = 0, + y = 0, + editable = true, + } + + local _ = {} -- internal members + _.handles = { } + _.elements = { } + _.focus = 1 + _.scrolling = false + _.lastEvent = 0 + + + function _.lcdSizeTextFixed(txt, font_size) + local ts_w, ts_h = lcd.sizeText(txt, font_size) + + local v_offset = 0 + if font_size == M.FONT_SIZES.FONT_38 then + v_offset = -11 + elseif font_size == M.FONT_SIZES.FONT_16 then + v_offset = -5 + elseif font_size == M.FONT_SIZES.FONT_12 then + v_offset = -4 + elseif font_size == M.FONT_SIZES.FONT_8 then + v_offset = -3 + elseif font_size == M.FONT_SIZES.FONT_6 then + v_offset = 0 + end + return ts_w, ts_h +2*v_offset, v_offset end - return gui.x + x, gui.y + y - end - - -- Replace lcd functions to translate by gui offset - function gui.drawCircle(x, y, r, flags) - x, y = gui.translate(x, y) - lcd.drawCircle(x, y, r, flags) - end - - function gui.drawFilledCircle(x, y, r, flags) - x, y = gui.translate(x, y) - lcd.drawFilledCircle(x, y, r, flags) - end - - function gui.drawLine(x1, y1, x2, y2, pattern, flags) - x1, y1 = gui.translate(x1, y1) - x2, y2 = gui.translate(x2, y2) - lcd.drawLine(x1, y1, x2, y2, pattern, flags) - end - - function gui.drawRectangle(x, y, w, h, flags, t) - x, y = gui.translate(x, y) - lcd.drawRectangle(x, y, w, h, flags, t) - end - - function gui.drawFilledRectangle(x, y, w, h, flags, opacity) - x, y = gui.translate(x, y) - lcd.drawFilledRectangle(x, y, w, h, flags, opacity) - end - - function gui.drawTriangle(x1, y1, x2, y2, x3, y3, flags) - x1, y1 = gui.translate(x1, y1) - x2, y2 = gui.translate(x2, y2) - x3, y3 = gui.translate(x3, y3) - lcd.drawTriangle(x1, y1, x2, y2, x3, y3, flags) - end - - function gui.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) - x1, y1 = gui.translate(x1, y1) - x2, y2 = gui.translate(x2, y2) - x3, y3 = gui.translate(x3, y3) - lcd.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) - end - - function gui.drawText(x, y, text, flags, inversColor) - x, y = gui.translate(x, y) - lcd.drawText(x, y, text, flags, inversColor) - end - - function gui.drawTextLines(x, y, w, h, text, flags) - x, y = gui.translate(x, y) - lcd.drawTextLines(x, y, w, h, text, flags) - end - - function gui.drawNumber(x, y, value, flags, inversColor) - x, y = gui.translate(x, y) - lcd.drawNumber(x, y, value, flags, inversColor) - end - - function gui.drawTimer(x, y, value, flags, inversColor) - x, y = gui.translate(x, y) - lcd.drawTimer(x, y, value, flags, inversColor) - end - - -- The default callBack - local function doNothing() - end - - -- The default changeValue - local function changeDefault(delta, self) - return self.value + delta - end - - -- Adjust text according to horizontal alignment - local function align(x, w, flags) - if bit32.band(flags, RIGHT) == RIGHT then - return x + w - elseif bit32.band(flags, CENTER) == CENTER then - return x + w / 2 - else - return x + + -- Translate coordinates for sub-GUIs + function gui.translate(x, y) + if gui.parent then + x, y = gui.parent.translate(x, y) + end + return gui.x + x, gui.y + y + end + + -- Replace lcd functions to translate by gui offset + function gui.drawCircle(x, y, r, flags) + x, y = gui.translate(x, y) + lcd.drawCircle(x, y, r, flags) end - end -- align(...) - - -- Draw border around focused elements - local function drawFocus(x, y, w, h, color) - -- Not necessary if there is only one element... - if #elements == 1 then - return + + function gui.drawFilledCircle(x, y, r, flags) + x, y = gui.translate(x, y) + lcd.drawFilledCircle(x, y, r, flags) end - color = color or lib.colors.active - gui.drawRectangle(x - 2, y - 2, w + 4, h + 4, color, 2) - end -- drawFocus(...) - - -- Move focus to another element - local function moveFocus(delta) - local count = 0 -- Prevent infinite loop - repeat - focus = focus + delta - if focus > #elements then - focus = 1 - elseif focus < 1 then - focus = #elements - end - count = count + 1 - until not (elements[focus].disabled or elements[focus].hidden) or count > #elements - end -- moveFocus(...) - - -- Moved the focused element - function gui.moveFocused(delta) - if delta > 0 then - delta = 1 - elseif delta < 0 then - delta = -1 + + function gui.drawLine(x1, y1, x2, y2, pattern, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + lcd.drawLine(x1, y1, x2, y2, pattern, flags) end - local idx = focus + delta - if idx >= 1 and idx <= #elements then - elements[focus], elements[idx] = elements[idx], elements[focus] - focus = idx + + function gui.drawRectangle(x, y, w, h, flags, t) + x, y = gui.translate(x, y) + lcd.drawRectangle(x, y, w, h, flags, t) end - end - - -- Add an element and return it to the client - local function addElement(element, x, y, w, h) - local idx = #elements + 1 - - if not element.covers then - function element.covers(p, q) - return (x <= p and p <= x + w and y <= q and q <= y + h) - end + + function gui.drawFilledRectangle(x, y, w, h, flags, opacity) + x, y = gui.translate(x, y) + lcd.drawFilledRectangle(x, y, w, h, flags, opacity) end - - elements[idx] = element - return element - end -- addElement(...) - - -- Add temporary BLINK or INVERS flags - local function getFlags(element) - local flags = element.flags - if element.blink then flags = bit32.bor(flags or 0, BLINK) end - if element.invers then flags = bit32.bor(flags or 0, INVERS) end - return flags - end - - -- Set an event handler - function gui.setEventHandler(event, f) - handles[event] = f - end - - -- Show prompt - function gui.showPrompt(prompt) - lib.prompt = prompt - end - - -- Dismiss prompt - function gui.dismissPrompt() - lib.prompt = nil - end - - -- Run an event cycle - function gui.run(event, touchState) - if not event then -- widget mode; event == nil - if lib.widgetRefresh then - lib.widgetRefresh() - else - gui.drawText(1, 1, "No widget refresh") - gui.drawText(1, 25, "function was loaded.") - end - else -- full screen mode; event is a value - gui.draw(false) - gui.onEvent(event, touchState) + + function gui.drawTriangle(x1, y1, x2, y2, x3, y3, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + x3, y3 = gui.translate(x3, y3) + lcd.drawTriangle(x1, y1, x2, y2, x3, y3, flags) end - lastEvent = event - end -- run(...) - function gui.draw(focused) - if gui.fullScreenRefresh then - gui.fullScreenRefresh() + function gui.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) + x1, y1 = gui.translate(x1, y1) + x2, y2 = gui.translate(x2, y2) + x3, y3 = gui.translate(x3, y3) + lcd.drawFilledTriangle(x1, y1, x2, y2, x3, y3, flags) + end + + function gui.drawText(x, y, text, flags, inversColor) + x, y = gui.translate(x, y) + local ts_w, ts_h, v_offset = _.lcdSizeTextFixed(text, M.FONT_SIZES.FONT_8) + lcd.drawText(x, y + v_offset, text, flags, inversColor) + --lcd.drawText(x, y, text, flags, inversColor) + end + + function gui.drawTextLines(x, y, w, h, text, flags) + x, y = gui.translate(x, y) + lcd.drawTextLines(x, y, w, h, text, flags) + end + + function gui.drawNumber(x, y, value, flags, inversColor) + x, y = gui.translate(x, y) + lcd.drawNumber(x, y, value, flags, inversColor) + end + + function gui.drawTimer(x, y, value, flags, inversColor) + x, y = gui.translate(x, y) + lcd.drawTimer(x, y, value, flags, inversColor) + end + + -- The default callBack + function _.doNothing() + end + + -- The default onChangeValue + function _.onChangeDefault(delta, self) + return self.value + delta + end + + -- Adjust text according to horizontal alignment + function _.align(x, w, flags) + if bit32.band(flags, RIGHT) == RIGHT then + return x + w + elseif bit32.band(flags, CENTER) == CENTER then + return x + w / 2 + else + return x + end + end -- align(...) + + -- Draw border around focused elements + local function drawFocus(x, y, w, h, color) + -- Not necessary if there is only one element... + if #_.elements == 1 then + return + end + color = color or M.colors.active + gui.drawRectangle(x - 4, y - 2, w + 8, h + 2, color, 2) + end -- drawFocus(...) + + -- Move focus to another element + local function moveFocus(delta) + local count = 0 -- Prevent infinite loop + repeat + _.focus = _.focus + delta + if _.focus > #_.elements then + _.focus = 1 + elseif _.focus < 1 then + _.focus = #_.elements + end + count = count + 1 + until not (_.elements[_.focus].disabled or _.elements[_.focus].hidden) or count > #_.elements + end -- moveFocus(...) + + -- Moved the focused element + function gui.moveFocused(delta) + if delta > 0 then + delta = 1 + elseif delta < 0 then + delta = -1 + end + local idx = _.focus + delta + if idx >= 1 and idx <= #_.elements then + _.elements[_.focus], _.elements[idx] = _.elements[idx], _.elements[_.focus] + _.focus = idx + end end - if focused then - if gui.parent.editing then - drawFocus(0, 0, gui.w, gui.h, lib.colors.edit) - else - drawFocus(0, 0, gui.w, gui.h) - end + + -- Add an element and return it to the client + local function addElement(element, x, y, w, h) + if not element.covers then + function element.covers(p, q) + return (x <= p and p <= x + w and y <= q and q <= y + h) + end + end + + _.elements[#_.elements+1] = element + return element + end -- addElement(...) + + -- Add temporary BLINK or INVERS flags + local function getFlags(element) + local flags = element.flags + if element.blink then flags = bit32.bor(flags or 0, BLINK) end + if element.invers then flags = bit32.bor(flags or 0, INVERS) end + return flags end - local guiFocus = not gui.parent or (focused and gui.parent.editing) - for idx, element in ipairs(elements) do - -- Clients may provide an update function for elements - if element.update then - element.update(element) - end - if not element.hidden then - element.draw(focus == idx and guiFocus) - end + + -- Set an event handler + function gui.setEventHandler(event, f) + _.handles[event] = f end - end -- draw() - - function gui.onEvent(event, touchState) - -- Make sure that focused element is active - if (elements[focus].disabled or elements[focus].hidden) then - moveFocus(1) - return + + -- Show prompt + function gui.showPrompt(prompt) + M.prompt = prompt end - -- Is there an active prompt? - if lib.prompt and not lib.showingPrompt then - lib.showingPrompt = true - lib.prompt.run(event, touchState) - lib.showingPrompt = false - return + + -- Dismiss prompt + function gui.dismissPrompt() + M.prompt = nil end - if event ~= 0 then -- non-zero event; process it - if not gui.parent or gui.parent.editing then + + ----------------------------------------------------------------------------------------------- + + -- Run an event cycle + function gui.run(event, touchState) + gui.draw(false) + if event ~= nil then + gui.onEvent(event, touchState) + end + _.lastEvent = event + end -- run(...) + + ----------------------------------------------------------------------------------------------- + + function gui.draw(focused) + if gui.fullScreenRefresh then + gui.fullScreenRefresh() + end + if focused then + if gui.parent.editing then + drawFocus(0, 0, gui.w, gui.h, M.colors.edit) + else + drawFocus(0, 0, gui.w, gui.h) + end + end + local guiFocus = not gui.parent or (focused and gui.parent.editing) + for idx, element in ipairs(_.elements) do + -- Clients may provide an update function for elements + if element.onUpdate then -- New name for method + element.onUpdate(element) + elseif element.update then -- For backward compatibility + element.update(element) + end + if not element.hidden then + element.draw(_.focus == idx and guiFocus) + end + end + end -- draw() + + ----------------------------------------------------------------------------------------------- + + function gui.onEvent(event, touchState) + -- Make sure that focused element is active + if (_.elements[_.focus].disabled or _.elements[_.focus].hidden) then + moveFocus(1) + return + end + -- Is there an active prompt? + if M.prompt and not M.showingPrompt then + M.showingPrompt = true + M.prompt.run(event, touchState) + M.showingPrompt = false + return + end + + if event == 0 then + return + end + + if gui.parent and not gui.parent.editing then + if event == EVT_VIRTUAL_ENTER then + gui.parent.editing = true + end + return + end + + -- non-zero event; process it -- Translate touch coordinates if offset if touchState then - touchState.x = touchState.x - gui.x - touchState.y = touchState.y - gui.y - if touchState.startX then - touchState.startX = touchState.startX - gui.x - touchState.startY = touchState.startY - gui.y - end - -- "Un-convert" ENTER to TAP - if event == EVT_VIRTUAL_ENTER then - event = EVT_TOUCH_TAP - end + touchState.x = touchState.x - gui.x + touchState.y = touchState.y - gui.y + if touchState.startX then + touchState.startX = touchState.startX - gui.x + touchState.startY = touchState.startY - gui.y + end + -- "Un-convert" ENTER to TAP + if event == EVT_VIRTUAL_ENTER then + event = EVT_TOUCH_TAP + end + end + + -- ETX 2.8 rc 4 bug fix + if _.scrolling and event == EVT_VIRTUAL_ENTER_LONG then + return end -- If we put a finger down on a menu item and immediately slide, then we can scroll if event == EVT_TOUCH_SLIDE then - if not scrolling then - return - end + if not _.scrolling then + return + end else - scrolling = false + _.scrolling = false end + -- "Pre-processing" of touch events to simplify subsequent handling and support scrolling etc. if event == EVT_TOUCH_FIRST then - if elements[focus].covers(touchState.x, touchState.y) then - scrolling = true - else - if gui.editing then - return + if _.elements[_.focus].covers(touchState.x, touchState.y) then + _.scrolling = true else - -- Did we touch another element? - for idx, element in ipairs(elements) do - if not (element.disabled or element.hidden) and element.covers(touchState.x, touchState.y) then - focus = idx - scrolling = true + if gui.editing then + return + else + -- Did we touch another element? + for idx, element in ipairs(_.elements) do + if not (element.disabled or element.hidden) and element.covers(touchState.x, touchState.y) then + _.focus = idx + _.scrolling = true + end + end end - end - end - end - elseif event == EVT_TOUCH_TAP or (event == EVT_TOUCH_BREAK and lastEvent == EVT_TOUCH_FIRST) then - if elements[focus].covers(touchState.x, touchState.y) then - -- Convert TAP on focused element to ENTER - event = EVT_VIRTUAL_ENTER - elseif gui.editing then - -- Convert a TAP off the element being edited to EXIT - event = EVT_VIRTUAL_EXIT - end - end - + end + elseif event == EVT_TOUCH_TAP or (event == EVT_TOUCH_BREAK and _.lastEvent == EVT_TOUCH_FIRST) then + if _.elements[_.focus].covers(touchState.x, touchState.y) then + -- Convert TAP on focused element to ENTER + event = EVT_VIRTUAL_ENTER + elseif gui.editing then + -- Convert a TAP off the element being edited to EXIT + event = EVT_VIRTUAL_EXIT + end + end + if gui.editing then -- Send the event directly to the element being edited - elements[focus].onEvent(event, touchState) + _.elements[_.focus].onEvent(event, touchState) elseif event == EVT_VIRTUAL_NEXT then -- Move focus - moveFocus(1) + moveFocus(1) elseif event == EVT_VIRTUAL_PREV then - moveFocus(-1) + moveFocus(-1) elseif event == EVT_VIRTUAL_EXIT and gui.parent then - gui.parent.editing = false + gui.parent.editing = false else - if handles[event] then - -- Is it being handled? Handler can modify event - event = handles[event](event, touchState) - -- If handler returned false or nil, then we are done - if not event then - return - end - end - elements[focus].onEvent(event, touchState) - end - elseif event == EVT_VIRTUAL_ENTER then - gui.parent.editing = true - end - end - end -- onEvent(...) - --- Create a text label - function gui.label(x, y, w, h, title, flags) - local self = { - title = title, - flags = bit32.bor(flags or lib.flags, VCENTER, lib.colors.primary1), - disabled = true - } - - function self.draw(focused) - local flags = getFlags(self) - gui.drawText(align(x, w, flags), y + h / 2, self.title, flags) - end + if _.handles[event] then + -- Is it being handled? Handler can modify event + event = _.handles[event](event, touchState) + -- If handler returned false or nil, then we are done + if not event then + return + end + end + _.elements[_.focus].onEvent(event, touchState) + end + end -- onEvent(...) - -- We should not ever onEvent, but just in case... - function self.onEvent(event, touchState) - self.disabled = true - moveFocus(1) - end - - function self.covers(p, q) - return false - end - - return addElement(self, x, y, w, h) - end -- label(...) - - -- Create a button to trigger a function - function gui.button(x, y, w, h, title, callBack, flags) - local self = { - title = title, - callBack = callBack or doNothing, - flags = bit32.bor(flags or lib.flags, CENTER, VCENTER) - } - - function self.draw(focused) - if focused then - drawFocus(x, y, w, h) - end - - gui.drawFilledRectangle(x, y, w, h, lib.colors.focus) - gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(lib.colors.primary2, self.flags)) - - if self.disabled then - gui.drawFilledRectangle(x, y, w, h, GREY, 7) - end - end - - function self.onEvent(event, touchState) - if event == EVT_VIRTUAL_ENTER then - return self.callBack(self) - end - end - - return addElement(self, x, y, w, h) - end -- button(...) - --- Create a toggle button that turns on/off. callBack gets true/false - function gui.toggleButton(x, y, w, h, title, value, callBack, flags) - local self = { - title = title, - value = value, - callBack = callBack or doNothing, - flags = bit32.bor(flags or lib.flags, CENTER, VCENTER) - } + ----------------------------------------------------------------------------------------------- - function self.draw(focused) - local fg = lib.colors.primary2 - local bg = lib.colors.focus - local border = lib.colors.active - - if self.value then - fg = lib.colors.primary3 - bg = lib.colors.active - border = lib.colors.focus - end - - if focused then - drawFocus(x, y, w, h, border) - end - - gui.drawFilledRectangle(x, y, w, h, bg) - gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(fg, self.flags)) - - if self.disabled then - gui.drawFilledRectangle(x, y, w, h, GREY, 7) - end - end - - function self.onEvent(event, touchState) - if event == EVT_VIRTUAL_ENTER then - self.value = not self.value - return self.callBack(self) - end - end - - return addElement(self, x, y, w, h) - end -- toggleButton(...) - --- Create a number that can be edited - function gui.number(x, y, w, h, value, changeValue, flags) - local self = { - value = value, - changeValue = changeValue or changeDefault, - flags = bit32.bor(flags or lib.flags, VCENTER), - editable = true - } - local d0 - - function self.draw(focused) - local flags = getFlags(self) - local fg = lib.colors.primary1 - - if focused then - drawFocus(x, y, w, h) - - if gui.editing then - fg = lib.colors.primary2 - gui.drawFilledRectangle(x, y, w, h, lib.colors.edit) - end - end - if type(self.value) == "string" then - gui.drawText(align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) - else - gui.drawNumber(align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) - end - end - - function self.onEvent(event, touchState) - if gui.editing then - if event == EVT_VIRTUAL_ENTER then - gui.editing = false - elseif event == EVT_VIRTUAL_EXIT then - self.value = value - gui.editing = false - elseif event == EVT_VIRTUAL_INC then - self.value = self.changeValue(1, self) - elseif event == EVT_VIRTUAL_DEC then - self.value = self.changeValue(-1, self) - elseif event == EVT_TOUCH_FIRST then - d0 = 0 - elseif event == EVT_TOUCH_SLIDE then - local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) - if d ~= d0 then - self.value = self.changeValue(d - d0, self) - d0 = d - end - end - elseif event == EVT_VIRTUAL_ENTER then - value = self.value - gui.editing = true - end - end -- onEvent(...) - - return addElement(self, x, y, w, h) - end -- number(...) - --- Create a display of current time on timer[tmr] --- Set timer.value to show a different value - function gui.timer(x, y, w, h, tmr, changeValue, flags) - local self = { - tmr = tmr, - changeValue = changeValue or changeDefault, - flags = bit32.bor(flags or lib.flags, VCENTER), - editable = true - } - local value - local d0 - - function self.draw(focused) - local flags = getFlags(self) - local fg = lib.colors.primary1 - -- self.value overrides the timer value - local value = self.value or model.getTimer(self.tmr).value - - if focused then - drawFocus(x, y, w, h) - - if gui.editing then - fg = lib.colors.primary2 - gui.drawFilledRectangle(x, y, w, h, lib.colors.edit) - end - end - if type(value) == "string" then - gui.drawText(align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) - else - gui.drawTimer(align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) - end - end - - function self.onEvent(event, touchState) - if gui.editing then - if event == EVT_VIRTUAL_ENTER then - if not value and self.tmr then - local tblTmr = model.getTimer(self.tmr) - tblTmr.value = self.value - model.setTimer(self.tmr, tblTmr) - self.value = nil - end - gui.editing = false - elseif event == EVT_VIRTUAL_EXIT then - self.value = value - gui.editing = false - elseif event == EVT_VIRTUAL_INC then - self.value = self.changeValue(1, self) - elseif event == EVT_VIRTUAL_DEC then - self.value = self.changeValue(-1, self) - elseif event == EVT_TOUCH_FIRST then - d0 = 0 - elseif event == EVT_TOUCH_SLIDE then - local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) - if d ~= d0 then - self.value = self.changeValue(d - d0, self) - d0 = d - end - end - elseif event == EVT_VIRTUAL_ENTER then - if self.value then - value = self.value - elseif self.tmr then - self.value = model.getTimer(self.tmr).value - value = nil - end - gui.editing = true - end - end -- onEvent(...) - - return addElement(self, x, y, w, h) - end -- timer(...) - - function gui.menu(x, y, w, h, items, callBack, flags) - local self = { - items = items or { "No items!" }, - flags = bit32.bor(flags or lib.flags, VCENTER), - editable = true, - selected = 1 - } - local selected = 1 - local firstVisible = 1 - local firstVisibleScrolling - local moving = 0 - local lh = select(2, lcd.sizeText("", self.flags)) - local visibleCount = math.floor(h / lh) - local killEvt - - callBack = callBack or doNothing - - local function setFirstVisible(v) - firstVisible = v - firstVisible = math.max(1, firstVisible) - firstVisible = math.min(#self.items - visibleCount + 1, firstVisible) - end - - local function adjustScroll() - if selected >= firstVisible + visibleCount then - firstVisible = selected - visibleCount + 1 - elseif selected < firstVisible then - firstVisible = selected - end - end + -- Create a text label + function gui.label(x, y, w, h, title, flags) + local self = { + title = title, + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), + disabled = true, + hidden= false, + } - function self.draw(focused) - local flags = getFlags(self) - local visibleCount = math.min(visibleCount, #self.items) - local sel - local bgColor - - if focused and gui.editing then - bgColor = lib.colors.edit - else - selected = self.selected - bgColor = lib.colors.focus - end - - for i = 0, visibleCount - 1 do - local j = firstVisible + i - local y = y + i * lh - - if j == selected then - gui.drawFilledRectangle(x, y, w, lh, bgColor) - gui.drawText(align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(lib.colors.primary2, flags)) - else - gui.drawText(align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(lib.colors.primary1, flags)) + function self.draw(focused) + local flags = getFlags(self) + gui.drawText(_.align(x, w, flags), y + h / 2, self.title, flags) end - end - if focused then - drawFocus(x, y, w, h) - end - end -- draw() - - function self.onEvent(event, touchState) - local visibleCount = math.min(visibleCount, #self.items) - - if moving ~= 0 then - if match(event, EVT_TOUCH_FIRST, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then - moving = 0 - event = 0 - else - setFirstVisible(firstVisible + moving) + -- We should not ever onEvent, but just in case... + function self.onEvent(event, touchState) + self.disabled = true + moveFocus(1) end - end - - if event ~= 0 then - -- This hack is needed because killEvents does not seem to work - if killEvt then - killEvt = false - if event == EVT_VIRTUAL_ENTER then - event = 0 - end + + function self.covers(p, q) + return false end - -- If we touch it, then start editing immediately - if touchState then - gui.editing = true + addElement(self, x, y, w, h) + return self + end -- label(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a text label lines + function gui.labelLines(x, y, w, h, title, flags) + local self = { + title = title, + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), + disabled = true, + hidden= false, + } + + function self.draw(focused) + local flags = getFlags(self) + gui.drawTextLines(_.align(x, w, flags), y , w, h, self.title, flags) end - if event == EVT_TOUCH_SLIDE then - if scrolling then - if touchState.swipeUp then - moving = 1 - elseif touchState.swipeDown then - moving = -1 - elseif touchState.startX then - setFirstVisible(firstVisibleScrolling + math.floor((touchState.startY - touchState.y) / lh + 0.5)) - end - end - else - scrolling = false - - if event == EVT_TOUCH_FIRST then - scrolling = true - firstVisibleScrolling = firstVisible - elseif match(event, EVT_VIRTUAL_NEXT, EVT_VIRTUAL_PREV) then - if event == EVT_VIRTUAL_NEXT then - selected = math.min(#self.items, selected + 1) - elseif event == EVT_VIRTUAL_PREV then - selected = math.max(1, selected - 1) - end - adjustScroll() - elseif event == EVT_VIRTUAL_ENTER then + -- We should not ever onEvent, but just in case... + function self.onEvent(event, touchState) + self.disabled = true + moveFocus(1) + end + + function self.covers(p, q) + return false + end + + addElement(self, x, y, w, h) + return self + end -- label(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a button to trigger a function + function gui.button(x, y, w, h, title, callBack, flags) + local self = { + title = title, + callBack = callBack or _.doNothing, + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), + disabled = false, + hidden= false + } + + function self.draw(focused) + if focused then + drawFocus(x, y, w, h) + end + + gui.drawFilledRectangle(x, y, w, h, M.colors.focus) + gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(M.colors.primary2, self.flags)) + + if self.disabled then + gui.drawFilledRectangle(x, y, w, h, GREY, 7) + end + end + + function self.onEvent(event, touchState) + if event == EVT_VIRTUAL_ENTER then + return self.callBack(self) + end + end + + addElement(self, x, y, w, h) + return self + end -- button(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a toggle button that turns on/off. callBack gets true/false + function gui.toggleButton(x, y, w, h, title, value, callBack, flags) + local self = { + title = title, + value = value, + callBack = callBack or _.doNothing, + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), + disabled = false, + hidden= false + } + + function self.draw(focused) + local fg = M.colors.primary2 + local bg = M.colors.focus + local border = M.colors.active + + if self.value then + fg = M.colors.primary3 + bg = M.colors.active + border = M.colors.focus + end + + if focused then + drawFocus(x, y, w, h, border) + end + + gui.drawFilledRectangle(x, y, w, h, bg) + gui.drawText(x + w / 2, y + h / 2, self.title, bit32.bor(fg, self.flags)) + + if self.disabled then + gui.drawFilledRectangle(x, y, w, h, GREY, 7) + end + end + + function self.onEvent(event, touchState) + if event == EVT_VIRTUAL_ENTER then + self.value = not self.value + return self.callBack(self) + end + end + + addElement(self, x, y, w, h) + return self + end -- toggleButton(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a number that can be edited + function gui.number(x, y, w, h, value, onChangeValue, flags, min, max) + local self = { + value = value, + onChangeValue = onChangeValue or _.onChangeDefault, + flags = bit32.bor(flags or M.flags, VCENTER), + editable = true, + disabled = false, + hidden= false, + min_val = min or 0, + max_val = max or 100, + } + + local d0 + + function self.draw(focused) + local flags = getFlags(self) + local fg = M.colors.primary1 + + if focused then + drawFocus(x, y, w, h) + + if gui.editing then + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) + end + end + if type(self.value) == "string" then + gui.drawText(_.align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) + else + gui.drawNumber(_.align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) + end + end + + function self.onEvent(event, touchState) if gui.editing then - if touchState then - selected = firstVisible + math.floor((touchState.y - y) / lh) - end - - gui.editing = false - self.selected = selected - callBack(self) + if event == EVT_VIRTUAL_ENTER then + gui.editing = false + elseif event == EVT_VIRTUAL_EXIT then + self.value = value + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + if self.value < self.max_val then + self.value = self.onChangeValue(1, self) + end + elseif event == EVT_VIRTUAL_DEC then + if self.value > self.min_val then + self.value = self.onChangeValue(-1, self) + end + elseif event == EVT_TOUCH_FIRST then + d0 = 0 + elseif event == EVT_TOUCH_SLIDE then + local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) + if d ~= d0 then + self.value = self.onChangeValue(d - d0, self) + d0 = d + end + end + elseif event == EVT_VIRTUAL_ENTER then + value = self.value + gui.editing = true + end + end -- onEvent(...) + + addElement(self, x, y, w, h) + return self + end -- number(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a display of current time on timer[tmr] + -- Set timer.value to show a different value + function gui.timer(x, y, w, h, tmr, onChangeValue, flags) + local self = { + tmr = tmr, + onChangeValue = onChangeValue or _.onChangeDefault, + flags = bit32.bor(flags or M.flags, VCENTER), + disabled = false, + hidden= false, + editable = true + } + local value + local d0 + + function self.draw(focused) + local flags = getFlags(self) + local fg = M.colors.primary1 + -- self.value overrides the timer value + local value = self.value or model.getTimer(self.tmr).value + + if focused then + drawFocus(x, y, w, h) + + if gui.editing then + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) + end + end + if type(value) == "string" then + gui.drawText(_.align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) else - gui.editing = true - selected = self.selected - adjustScroll() + gui.drawTimer(_.align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) end - elseif event == EVT_VIRTUAL_EXIT then - gui.editing = false - end end - end - end -- onEvent(...) - return addElement(self, x, y, w, h) - end -- menu(...) - - function gui.dropDown(x, y, w, h, items, selected, callBack, flags) - callBack = callBack or doNothing - flags = flags or lib.flags - - local self - local showingMenu - local drawingMenu - local dropDown = lib.newGUI() - local lh = select(2, lcd.sizeText("", flags)) - local height = math.min(0.75 * LCD_H, #items * lh) - local top = (LCD_H - height) / 2 - - dropDown.x = gui.translate(0, 0) - top = math.min(top, y) - top = math.max(top, y + h - height) - - local function dismissMenu() - showingMenu = false - gui.dismissPrompt() - end - - function dropDown.fullScreenRefresh() - if not dropDown.editing then - dismissMenu() - return - end - dropDown.drawFilledRectangle(x, top, w, height, lib.colors.primary2) - dropDown.drawRectangle(x - 2, top - 2, w + 4, height + 4, lib.colors.primary1, 2) - drawingMenu = true - end - - local function onMenu(menu) - dismissMenu() - callBack(self) - end - - self = dropDown.menu(x, top, w, height, items, onMenu, flags) - self.selected = selected - local drawMenu = self.draw - - function self.draw(focused) - if drawingMenu then - drawingMenu = false - drawMenu(focused) - else - local flags = bit32.bor(VCENTER, lib.colors.primary1, getFlags(self)) + function self.onEvent(event, touchState) + if gui.editing then + if event == EVT_VIRTUAL_ENTER then + if not value and self.tmr then + local tblTmr = model.getTimer(self.tmr) + tblTmr.value = self.value + model.setTimer(self.tmr, tblTmr) + self.value = nil + end + gui.editing = false + elseif event == EVT_VIRTUAL_EXIT then + self.value = value + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = self.onChangeValue(1, self) + elseif event == EVT_VIRTUAL_DEC then + self.value = self.onChangeValue(-1, self) + elseif event == EVT_TOUCH_FIRST then + d0 = 0 + elseif event == EVT_TOUCH_SLIDE then + local d = math.floor((touchState.startY - touchState.y) / 20 + 0.5) + if d ~= d0 then + self.value = self.onChangeValue(d - d0, self) + d0 = d + end + end + elseif event == EVT_VIRTUAL_ENTER then + if self.value then + value = self.value + elseif self.tmr then + self.value = model.getTimer(self.tmr).value + value = nil + end + gui.editing = true + end + end -- onEvent(...) - if focused then - drawFocus(x, y, w, h) - end - gui.drawText(align(x, w, flags), y + h / 2, self.items[self.selected], flags) - local dd = lh / 2 - local yy = y + (h - dd) / 2 - local xx = x + w - 1.15 * dd - gui.drawTriangle(x + w, yy, (x + w + xx) / 2, yy + dd, xx, yy, lib.colors.primary1) - end - end + addElement(self, x, y, w, h) + return self + end -- timer(...) - local onMenu = self.onEvent - - function self.onEvent(event, touchState) - if showingMenu then - onMenu(event, touchState) - elseif event == EVT_VIRTUAL_ENTER then - -- Show drop down and let it take over while active - showingMenu = true - dropDown.onEvent(event) - gui.showPrompt(dropDown) - else - end - end - - local coverMenu = self.covers - - function self.covers(p, q) - if showingMenu then - return coverMenu(p, q) - else - return (x <= p and p <= x + w and y <= q and q <= y + h) - end - end - - return addElement(self, x, y, w, h) - end -- dropDown(...) - - function gui.horizontalSlider(x, y, w, value, min, max, delta, callBack) - local self = { - value = value, - min = min, - max = max, - delta = delta, - callBack = callBack or doNothing, - editable = true - } + ----------------------------------------------------------------------------------------------- - function self.draw(focused) - local xdot = x + w * (self.value - self.min) / (self.max - self.min) - - local colorBar = lib.colors.primary3 - local colorDot = lib.colors.primary2 - local colorDotBorder = lib.colors.primary3 - - if focused then - colorDotBorder = lib.colors.active - if gui.editing or scrolling then - colorBar = lib.colors.primary1 - colorDot = lib.colors.edit - end - end - - gui.drawFilledRectangle(x, y - 2, w, 5, colorBar) - gui.drawFilledCircle(xdot, y, SLIDER_DOT_RADIUS, colorDot) - for i = -1, 1 do - gui.drawCircle(xdot, y, SLIDER_DOT_RADIUS + i, colorDotBorder) - end - end - - function self.onEvent(event, touchState) - local v0 = self.value - - if gui.editing then - if match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then - gui.editing = false - elseif event == EVT_VIRTUAL_INC then - self.value = math.min(self.max, self.value + self.delta) - elseif event == EVT_VIRTUAL_DEC then - self.value = math.max(self.min, self.value - self.delta) - end - elseif event == EVT_VIRTUAL_ENTER then - gui.editing = true - end - - if event == EVT_TOUCH_SLIDE then - local value = self.min + (self.max - self.min) * (touchState.x - x) / w - value = math.min(self.max, value) - value = math.max(self.min, value) - self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) - end - - if v0 ~= self.value then - self.callBack(self) - end - end - - function self.covers(p, q) - local xdot = x + w * (self.value - self.min) / (self.max - self.min) - return ((p - xdot)^2 + (q - y)^2 <= 2 * SLIDER_DOT_RADIUS^2) - end - - return addElement(self) - end -- horizontalSlider(...) - - function gui.verticalSlider(x, y, h, value, min, max, delta, callBack) - local self = { - value = value, - min = min, - max = max, - delta = delta, - callBack = callBack or doNothing, - editable = true - } + function gui.menu(x, y, w, h, items, callBack, flags) + local self = { + items = items or { "No items!" }, + flags = bit32.bor(flags or M.flags, VCENTER), + disabled = false, + hidden= false, + editable = true, + selected = 1 + } + local selected = 1 + local firstVisible = 1 + local firstVisibleScrolling + local moving = 0 + local lh = select(2, lcd.sizeText("", self.flags)) + local visibleCount = math.floor(h / lh) + local killEvt - function self.draw(focused) - local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) - - local colorBar = lib.colors.primary3 - local colorDot = lib.colors.primary2 - local colorDotBorder = lib.colors.primary3 - - if focused then - colorDotBorder = lib.colors.active - if gui.editing or scrolling then - colorBar = lib.colors.primary1 - colorDot = lib.colors.edit - end - end - - gui.drawFilledRectangle(x - 2, y, 5, h, colorBar) - gui.drawFilledCircle(x, ydot, SLIDER_DOT_RADIUS, colorDot) - for i = -1, 1 do - gui.drawCircle(x, ydot, SLIDER_DOT_RADIUS + i, colorDotBorder) - end - end - - function self.onEvent(event, touchState) - local v0 = self.value - - if gui.editing then - if match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then - gui.editing = false - elseif event == EVT_VIRTUAL_INC then - self.value = math.min(self.max, self.value + self.delta) - elseif event == EVT_VIRTUAL_DEC then - self.value = math.max(self.min, self.value - self.delta) - end - elseif event == EVT_VIRTUAL_ENTER then - gui.editing = true - end - - if event == EVT_TOUCH_SLIDE then - local value = self.max - (self.max - self.min) * (touchState.y - y) / h - value = math.min(self.max, value) - value = math.max(self.min, value) - self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) - end - - if v0 ~= self.value then - self.callBack(self) - end - end - - function self.covers(p, q) - local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) - return ((p - x)^2 + (q - ydot)^2 <= 2 * SLIDER_DOT_RADIUS^2) - end - - return addElement(self) - end -- verticalSlider(...) - - -- Create a custom element - function gui.custom(self, x, y, w, h) - self.gui = gui - self.lib = lib - - function self.drawFocus(color) - drawFocus(self.x or x, self.y or y, self.w or w, self.h or h, color) - end - - -- Must be implemented by the client - if not self.draw then - function self.draw(focused) - gui.drawText(x, y, "draw(focused) missing") - if focused then - drawFocus(x, y, w, h) + callBack = callBack or _.doNothing + + local function setFirstVisible(v) + firstVisible = v + firstVisible = math.max(1, firstVisible) + firstVisible = math.min(#self.items - visibleCount + 1, firstVisible) end - end - end - - -- Must be implemented by the client - if not self.onEvent then - function self.onEvent() - playTone(200, 200, 0, PLAY_NOW) - end + + local function adjustScroll() + if selected >= firstVisible + visibleCount then + firstVisible = selected - visibleCount + 1 + elseif selected < firstVisible then + firstVisible = selected + end + end + + function self.draw(focused) + local flags = getFlags(self) + local visibleCount = math.min(visibleCount, #self.items) + local sel + local bgColor + + if focused and gui.editing then + bgColor = M.colors.edit + else + selected = self.selected + bgColor = M.colors.focus + end + + for i = 0, visibleCount - 1 do + local j = firstVisible + i + local y = y + i * lh + + if j == selected then + gui.drawFilledRectangle(x, y, w, lh, bgColor) + gui.drawText(_.align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary2, flags)) + else + gui.drawText(_.align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary1, flags)) + end + end + + if focused then + drawFocus(x, y, w, h) + end + end -- draw() + + function self.onEvent(event, touchState) + local visibleCount = math.min(visibleCount, #self.items) + + if moving ~= 0 then + if M.match(event, EVT_TOUCH_FIRST, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + moving = 0 + event = 0 + else + setFirstVisible(firstVisible + moving) + end + end + + if event ~= 0 then + -- This hack is needed because killEvents does not seem to work + if killEvt then + killEvt = false + if event == EVT_VIRTUAL_ENTER then + event = 0 + end + end + + -- If we touch it, then start editing immediately + if touchState then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + if _.scrolling then + if touchState.swipeUp then + moving = 1 + elseif touchState.swipeDown then + moving = -1 + elseif touchState.startX then + setFirstVisible(firstVisibleScrolling + math.floor((touchState.startY - touchState.y) / lh + 0.5)) + end + end + else + _.scrolling = false + + if event == EVT_TOUCH_FIRST then + _.scrolling = true + firstVisibleScrolling = firstVisible + elseif M.match(event, EVT_VIRTUAL_NEXT, EVT_VIRTUAL_PREV) then + if event == EVT_VIRTUAL_NEXT then + selected = math.min(#self.items, selected + 1) + elseif event == EVT_VIRTUAL_PREV then + selected = math.max(1, selected - 1) + end + adjustScroll() + elseif event == EVT_VIRTUAL_ENTER then + if gui.editing then + if touchState then + selected = firstVisible + math.floor((touchState.y - y) / lh) + end + + gui.editing = false + self.selected = selected + callBack(self) + else + gui.editing = true + selected = self.selected + adjustScroll() + end + elseif event == EVT_VIRTUAL_EXIT then + gui.editing = false + end + end + end + end -- onEvent(...) + + addElement(self, x, y, w, h) + return self + end -- menu(...) + + ----------------------------------------------------------------------------------------------- + + function gui.dropDown(x, y, w, h, items, selected, callBack, flags) + callBack = callBack or _.doNothing + flags = flags or M.flags + + local self + local showingMenu + local drawingMenu + local dropDown = M.newGUI() + local lh = select(2, lcd.sizeText("", flags)) + local height = math.min(0.75 * LCD_H, #items * lh) + local top = (LCD_H - height) / 2 + + dropDown.x = gui.translate(0, 0) + top = math.min(top, y) + top = math.max(top, y + h - height) + + local function dismissMenu() + showingMenu = false + gui.dismissPrompt() + end + + function dropDown.fullScreenRefresh() + if not dropDown.editing then + dismissMenu() + return + end + dropDown.drawFilledRectangle(x, top, w, height, M.colors.primary2) + dropDown.drawRectangle(x - 2, top - 2, w + 4, height + 4, M.colors.primary1, 2) + drawingMenu = true + end + + local function onMenu(menu) + dismissMenu() + callBack(self) + end + + self = dropDown.menu(x, top, w, height, items, onMenu, flags) + self.selected = selected + local drawMenu = self.draw + + function self.draw(focused) + if drawingMenu then + drawingMenu = false + drawMenu(focused) + else + local flags = bit32.bor(VCENTER, M.colors.primary1, getFlags(self)) + + if focused then + drawFocus(x, y, w, h) + end + gui.drawText(_.align(x, w, flags), y + h / 2, self.items[self.selected], flags) + local dd = lh / 2 + local yy = y + (h - dd) / 2 + local xx = (x-5) + w - 1.15 * dd + gui.drawTriangle(x-5 + w, yy, (x-5 + w + xx) / 2, yy + dd, xx, yy, M.colors.primary1) + end + end + + local onMenu = self.onEvent + + function self.onEvent(event, touchState) + if showingMenu then + onMenu(event, touchState) + elseif event == EVT_VIRTUAL_ENTER then + -- Show drop down and let it take over while active + showingMenu = true + dropDown.onEvent(event) + gui.showPrompt(dropDown) + else + end + end + + local coverMenu = self.covers + + function self.covers(p, q) + if showingMenu then + return coverMenu(p, q) + else + return (x <= p and p <= x + w and y <= q and q <= y + h) + end + end + + addElement(self, x, y, w, h) + return self + end -- dropDown(...) + + ----------------------------------------------------------------------------------------------- + + function gui.horizontalSlider(x, y, w, value, min, max, delta, callBack) + local self = { + value = value, + min = min, + max = max, + delta = delta, + callBack = callBack or _.doNothing, + disabled = false, + hidden= false, + editable = true + } + + function self.draw(focused) + local xdot = x + w * (self.value - self.min) / (self.max - self.min) + + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 + + if focused then + colorDotBorder = M.colors.active + if gui.editing or _.scrolling then + colorBar = M.colors.primary1 + colorDot = M.colors.edit + end + end + + gui.drawFilledRectangle(x, y - 2, w, 5, colorBar) + gui.drawFilledCircle(xdot, y, M.SLIDER_DOT_RADIUS, colorDot) + for i = -1, 1 do + gui.drawCircle(xdot, y, M.SLIDER_DOT_RADIUS + i, colorDotBorder) + end + end + + function self.onEvent(event, touchState) + local v0 = self.value + + if gui.editing then + if M.match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = math.min(self.max, self.value + self.delta) + elseif event == EVT_VIRTUAL_DEC then + self.value = math.max(self.min, self.value - self.delta) + end + elseif event == EVT_VIRTUAL_ENTER then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + local value = self.min + (self.max - self.min) * (touchState.x - x) / w + value = math.min(self.max, value) + value = math.max(self.min, value) + self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) + end + + if v0 ~= self.value then + self.callBack(self) + end + end + + function self.covers(p, q) + local xdot = x + w * (self.value - self.min) / (self.max - self.min) + return ((p - xdot) ^ 2 + (q - y) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) + end + + addElement(self) + return self + end -- horizontalSlider(...) + + ----------------------------------------------------------------------------------------------- + + function gui.verticalSlider(x, y, h, value, min, max, delta, callBack) + local self = { + value = value, + min = min, + max = max, + delta = delta, + callBack = callBack or _.doNothing, + disabled = false, + hidden= false, + editable = true + } + + function self.draw(focused) + local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) + + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 + + if focused then + colorDotBorder = M.colors.active + if gui.editing or _.scrolling then + colorBar = M.colors.primary1 + colorDot = M.colors.edit + end + end + + gui.drawFilledRectangle(x - 2, y, 5, h, colorBar) + gui.drawFilledCircle(x, ydot, M.SLIDER_DOT_RADIUS, colorDot) + for i = -1, 1 do + gui.drawCircle(x, ydot, M.SLIDER_DOT_RADIUS + i, colorDotBorder) + end + end + + function self.onEvent(event, touchState) + local v0 = self.value + + if gui.editing then + if M.match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + gui.editing = false + elseif event == EVT_VIRTUAL_INC then + self.value = math.min(self.max, self.value + self.delta) + elseif event == EVT_VIRTUAL_DEC then + self.value = math.max(self.min, self.value - self.delta) + end + elseif event == EVT_VIRTUAL_ENTER then + gui.editing = true + end + + if event == EVT_TOUCH_SLIDE then + local value = self.max - (self.max - self.min) * (touchState.y - y) / h + value = math.min(self.max, value) + value = math.max(self.min, value) + self.value = self.min + self.delta * math.floor((value - self.min) / self.delta + 0.5) + end + + if v0 ~= self.value then + self.callBack(self) + end + end + + function self.covers(p, q) + local ydot = y + h * (1 - (self.value - self.min) / (self.max - self.min)) + return ((p - x) ^ 2 + (q - ydot) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) + end + + addElement(self) + return self + end -- verticalSlider(...) + + ----------------------------------------------------------------------------------------------- + + -- Create a custom element + function gui.custom(self, x, y, w, h) + self.gui = gui + self.lib = M + + function self.drawFocus(color) + drawFocus(self.x or x, self.y or y, self.w or w, self.h or h, color) + end + + -- Must be implemented by the client + if not self.draw then + function self.draw(focused) + gui.drawText(x, y, "draw(focused) missing") + if focused then + drawFocus(x, y, w, h) + end + end + end + + -- Must be implemented by the client + if not self.onEvent then + function self.onEvent() + playTone(200, 200, 0, PLAY_NOW) + end + end + + addElement(self, x, y, w, h) + return self end - - return addElement(self, x, y, w, h) - end - - -- Create a nested gui - function gui.gui(x, y, w, h) - local self = lib.newGUI() - self.parent = gui - self.editing = false - self.x, self.y, self.w, self.h = x, y, w, h - - function self.covers(p, q) - return (self.x <= p and p <= self.x + self.w and self.y <= q and q <= self.y + self.h) + + ----------------------------------------------------------------------------------------------- + + -- Create a nested gui + function gui.gui(x, y, w, h) + local self = M.newGUI() + self.parent = gui + self.editing = false + self.x, self.y, self.w, self.h = x, y, w, h + + function self.covers(p, q) + return (self.x <= p and p <= self.x + self.w and self.y <= q and q <= self.y + self.h) + end + + addElement(self, x, y, w, h) + return self end - return addElement(self, x, y, w, h) - end - - return gui + + ----------------------------------------------------------------------------------------------- + return gui + end -- gui(...) -return lib \ No newline at end of file +return M