From 0b2f353e1339a5eeae95308258a4c474776da4c6 Mon Sep 17 00:00:00 2001 From: Shmuely Date: Tue, 26 Apr 2022 08:36:59 +0300 Subject: [PATCH 1/7] new Rotary Gauge widget --- .../horus/WIDGETS/GaugeRotary/gauge_core.lua | 165 ++++++++++ sdcard/horus/WIDGETS/GaugeRotary/main.lua | 303 ++++++++++++++++++ 2 files changed, 468 insertions(+) create mode 100644 sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua create mode 100644 sdcard/horus/WIDGETS/GaugeRotary/main.lua diff --git a/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua new file mode 100644 index 00000000..7386f564 --- /dev/null +++ b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua @@ -0,0 +1,165 @@ +local HighAsGreen, p2 = ... + +local self = {} +self.HighAsGreen = HighAsGreen + +-------------------------------------------------------------- +local function log(s) + --return; + print("Gauge_core: " .. s) +end +-------------------------------------------------------------- + +function self.drawArm(armX, armY, armR, percentageValue, color, isFull) + --min = 5.54 + --max = 0.8 + + local degrees + if isFull then + degrees = 5.51 - (4.74 * percentageValue / 100) + else + --degrees = 4.74 - (3.14 * percentageValue / 100) + degrees = 5.05 - (3.84 * percentageValue / 100) + end + + --log("percentageValue: " .. percentageValue .. ", degrees: " .. degrees) + local xh = math.floor(armX + (math.sin(degrees) * armR)) + local yh = math.floor(armY + (math.cos(degrees) * armR)) + + --lcd.setColor(CUSTOM_COLOR, lcd.RGB(0, 0, 255)) + --lcd.setColor(CUSTOM_COLOR, lcd.RGB(255, 255, 255)) + lcd.setColor(CUSTOM_COLOR, color) + + local x1 = math.floor(armX - (math.sin(0) * (20 / 2.3))) + local y1 = math.floor(armY - (math.cos(0) * (20 / 2.3))) + local x2 = math.floor(armX - (math.sin(3) * (20 / 2.3))) + local y2 = math.floor(armY - (math.cos(3) * (20 / 2.3))) + lcd.drawFilledTriangle(x1, y1, x2, y2, xh, yh, CUSTOM_COLOR) +end + +-- This function returns green at gvalue, red at rvalue and graduate in between +function self.getRangeColor(value, red_value, green_value) + local range = math.abs(green_value - red_value) + if range == 0 then + return lcd.RGB(0, 0xdf, 0) + end + if value == nil then + return lcd.RGB(0, 0xdf, 0) + end + + if green_value > red_value then + if value > green_value then + return lcd.RGB(0, 0xdf, 0) + end + if value < red_value then + return lcd.RGB(0xdf, 0, 0) + end + g = math.floor(0xdf * (value - red_value) / range) + r = 0xdf - g + return lcd.RGB(r, g, 0) + else + if value < green_value then + return lcd.RGB(0, 0xdf, 0) + end + if value > red_value then + return lcd.RGB(0xdf, 0, 0) + end + r = math.floor(0xdf * (value - green_value) / range) + g = 0xdf - r + return lcd.RGB(r, g, 0) + end +end + +function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, percentageValueMin, percentageValueMax, txt1, txt2) + + local fender = 4 + local tickWidth = 9 + local armCenterR = centreR / 2.5 + local armR = centreR - 8 + local txtSize = DBLSIZE + if centreR < 65 then + txtSize = MIDSIZE + end + if centreR < 30 then + txtSize = SMLSIZE + end + + -- main gauge background + if isFull then + lcd.drawFilledCircle(centerX, centerY, centreR, lcd.RGB(0x1A1A1A)) + else + lcd.drawPie(centerX,centerY,centreR, -110,110, lcd.RGB(0x1A1A1A)) + end + + -- fender + if isFull then + lcd.drawAnnulus(centerX, centerY, centreR - fender, centreR, 0, 360, BLACK) + else + lcd.drawAnnulus(centerX, centerY, centreR - fender, centreR, -110, 110, BLACK) + end + + -- ticks + local to_tick + local tick_offset + if isFull then + to_tick = 210 + tick_offset = 250 + else + to_tick = 210 + tick_offset = 250 + end + + for i = 0, to_tick, 10 do + --log("HighAsGreen: " .. self.HighAsGreen) + if (self.HighAsGreen == 1) then + lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, 0, to_tick - 10)) + else + lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, to_tick - 10, 0)) + --lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, 120 , 30)) + end + lcd.drawAnnulus(centerX, centerY, centreR - fender - 3 - tickWidth, centreR - fender - 3, tick_offset + i, tick_offset + i + 7, CUSTOM_COLOR) + --lcd.drawAnnulus(centerX, centerY, centreR -fender -3 -tickWidth, centreR -fender -3 , 250 +i, 250 +i +7, YELLOW) + --lcd.drawAnnulus(centerX, centerY, centreR -fender -3 -tickWidth -15, centreR -fender -3 -tickWidth -4 , 250 +i, 250 +i +7, RED) + end + --lcd.drawPie(centerX,centerY,centreR - fender, 0,20) + + local armColor = lcd.RGB(255, 255, 255) + local armColorMin, armColorMax + if (self.HighAsGreen == 1) then + armColorMin = lcd.RGB(100, 0, 0) + armColorMax = lcd.RGB(0, 100, 0) + else + armColorMin = lcd.RGB(0, 100, 0) + armColorMax = lcd.RGB(100, 0, 0) + end + + --self.drawArm(centerX, centerY, armR, 0, armColorMin, isFull) + --self.drawArm(centerX, centerY, armR, 10, armColorMin, isFull) + --self.drawArm(centerX, centerY, armR, 50, armColorMin, isFull) + --self.drawArm(centerX, centerY, armR, 90, armColorMin, isFull) + --self.drawArm(centerX, centerY, armR, 100, armColorMin, isFull) + + if percentageValueMin ~= nil and percentageValueMax ~= nil then + self.drawArm(centerX, centerY, armR, percentageValueMin, armColorMin, isFull) + self.drawArm(centerX, centerY, armR, percentageValueMax, armColorMax, isFull) + end + self.drawArm(centerX, centerY, armR, percentageValue, armColor, isFull) + + -- hide the base of the arm + lcd.drawFilledCircle(centerX, centerY, armCenterR, BLACK) + + if isFull then + else + end + + -- text in center + lcd.drawText(centerX + 0, centerY - 8, txt2, CENTER + SMLSIZE + WHITE) -- XXLSIZE/DBLSIZE/MIDSIZE/SMLSIZE + if isFull then + -- text below + lcd.drawText(centerX + 8, centerY + 30, txt1, CENTER + txtSize + WHITE) + else + end + +end + +return self \ No newline at end of file diff --git a/sdcard/horus/WIDGETS/GaugeRotary/main.lua b/sdcard/horus/WIDGETS/GaugeRotary/main.lua new file mode 100644 index 00000000..cf020c9c --- /dev/null +++ b/sdcard/horus/WIDGETS/GaugeRotary/main.lua @@ -0,0 +1,303 @@ +---- ######################################################################### +---- # # +---- # Telemetry Widget script for FrSky Horus/RadioMaster TX16s # +---- # 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. # +---- # # +---- ######################################################################### + +-- This GaugeRotary widget display a fancy old style analog gauge with needle +-- Options: +-- HighAsGreen: [checked] for sensor that high values is good (RSSI/Fuel/...) +-- [checked] for sensor that high values is good (RSSI/Fuel/...) +-- [un-checked] for sensor that low values is good (Temp/Battery/...) +-- Note: if the min & max input value are -1, widget will automatically select min/max based on the source name. +-- common sources are: +-- * RSSI +-- * Temp +-- * rpm +-- * fuel +-- * vibration (heli) +-- * Transmitter Battery +-- * batt-capacity +-- * A1/A2 analog voltage + +-- Version: 0.1 +-- Author : Offer Shmuely + +local UNIT_ID_TO_STRING = { "V", "A", "mA", "kts", "m/s", "f/s", "km/h", "mph", "m", "f", "°C", "°F", "%", "mAh", "W", "mW", "dB", "rpm", "g", "°", "rad", "ml", "fOz", "ml/m", "Hz", "uS", "km" } +local DEFAULT_MIN_MAX = { + {"RSSI" , 0, 100, 0}, + {"RxBt" , 4, 10, 1}, + {"TxBt" , 6, 8.4, 1}, + {"Batt" , 6, 8.4, 1}, + {"cell" ,3.5, 4.2, 1}, + {"Fuel" , 0, 100, 0}, + {"Vibr" , 0, 100, 0}, + {"Temp" , 30,120, 0}, + {"Tmp1" , 30,120, 0}, + {"Tmp2" , 30,120, 0}, +} + +local _options = { + { "Source", SOURCE, 253 }, -- RSSI + --{ "Source", SOURCE, 243 }, -- TxBt + --{ "Source", SOURCE, 256 }, -- RxBt + { "Min", VALUE, -1, -1024, 1024 }, + { "Max", VALUE, -1, -1024, 1024 }, + { "HighAsGreen", BOOL, 1 }, + { "Precision", VALUE, 1 , 0 , 1} +} + +-------------------------------------------------------------- +local function log(s) + return; + --print("GaugeRotary: " .. s) +end +-------------------------------------------------------------- + +local function setAutoMinMax(wgt) + if wgt.options.Min ~= -1 and wgt.options.Max ~= -1 then + --if wgt.options.Min ~= wgt.options.Max then + print("GaugeRotary-setting: " .. "no need for AutoMinMax") + return + end + + print("GaugeRotary-setting: " .. "AutoMinMax") + local sourceName = getSourceName(wgt.options.Source) + -- workaround for bug in getFiledInfo() + if string.byte(string.sub(sourceName,1,1)) > 127 then + sourceName = string.sub(sourceName,2,-1) -- ???? why? + end + print("GaugeRotary-setting: " .. "AutoMinMax, source:" .. sourceName) + + for i=1, #DEFAULT_MIN_MAX, 1 do + local def_key = DEFAULT_MIN_MAX[i][1] + local def_min = DEFAULT_MIN_MAX[i][2] + local def_max = DEFAULT_MIN_MAX[i][3] + local def_precision = DEFAULT_MIN_MAX[i][4] + + if def_key == sourceName then + log(string.format("setting min-max from default: %s: min:%d, max:%d, precision:%d", def_key, def_min, def_max, def_precision)) + wgt.options.Min = def_min + wgt.options.Max = def_max + wgt.options.precision = def_precision + break + end + end + + if wgt.options.Min == wgt.options.Max then + print("GaugeRotary-setting: " .. "AutoMinMax else") + wgt.options.Min = 0 + wgt.options.Max = 100 + end + +end + +local function create(zone, options) + local GaugeClass = loadScript("/WIDGETS/GaugeRotary/gauge_core.lua") + + local wgt = { + zone = zone, + options = options, + gauge1 = GaugeClass(options.HighAsGreen, 2) + } + + setAutoMinMax(wgt) + + return wgt +end + +local function update(wgt, options) + wgt.options = options + setAutoMinMax(wgt) + wgt.gauge1.HighAsGreen = wgt.options.HighAsGreen +end + +-- ----------------------------------------------------------------------------------------------------- + +local function getPercentageValue(value, options_min, options_max) + if value == nil then + return nil + end + + local percentageValue = value - options_min; + percentageValue = (percentageValue / (options_max - options_min)) * 100 + percentageValue = tonumber(percentageValue) + percentageValue = math.floor( percentageValue ) + + if percentageValue > 100 then + percentageValue = 100 + elseif percentageValue < 0 then + percentageValue = 0 + end + + log("getPercentageValue(" .. value .. ", " .. options_min .. ", " .. options_max .. ")-->" .. percentageValue) + return percentageValue +end + +local function getWidgetValue(wgt) + local currentValue = getValue(wgt.options.Source) + local sourceName = getSourceName(wgt.options.Source) + log("aaaaaa: ".. sourceName) + log("aaaaaa: ".. sourceName .. ": " .. string.byte(string.sub(sourceName, 1, 1))) + + -- workaround for bug in getFiledInfo() + if string.byte(string.sub(sourceName,1,1)) > 127 then + sourceName = string.sub(sourceName,2,-1) -- ???? why? + end + --log("Source: " .. wgt.options.Source .. ",name: " .. sourceName) + + --local currentValue = getValue(wgt.options.Source) / 10.24 + + local fieldinfo = getFieldInfo(wgt.options.Source) + if (fieldinfo == nil) then + log(string.format("getFieldInfo(%s)==nil", wgt.options.Source)) + return sourceName, -1, nil, nil, "" + end + + local txtUnit = "-" + if (fieldinfo.unit) then + --log("have unit") + if (fieldinfo.unit > 0 and fieldinfo.unit < #UNIT_ID_TO_STRING) then + txtUnit = UNIT_ID_TO_STRING[fieldinfo.unit] + end + end + + log("") + log(string.format("id: %s", fieldinfo.id)) + log(string.format(" sourceName: %s", sourceName)) + log(string.format(" curr: %2.1f", currentValue)) + log(string.format(" name: %s", fieldinfo.name)) + log(string.format(" desc: %s", fieldinfo.desc)) + log(string.format(" idUnit: %s", fieldinfo.unit)) + log(string.format(" txtUnit: %s", txtUnit)) + + -- try to get min/max value (if exist) + local minValue = getValue(sourceName .. "-") + local maxValue = getValue(sourceName .. "+") + --log("min/max: " .. minValue .. " < " .. currentValue .. " < " .. maxValue) + + return sourceName, currentValue, minValue, maxValue, txtUnit +end + +local function refresh_app_mode(wgt, event, touchState, w_name, value, minValue, maxValue, w_unit, percentageValue, percentageValueMin, percentageValueMax) + local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) + if (value == nil) then + return + end + + local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) + local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) + local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) + + local zone_w = 460 + local zone_h = 252 + + local centerX = zone_w / 2 + wgt.gauge1.drawGauge(centerX, 120, 110, false, percentageValue, percentageValueMin, percentageValueMax, percentageValue .. w_unit, w_name) + lcd.drawText(10, 10, string.format("%d%s", percentageValue, w_unit), XXLSIZE + YELLOW) + + -- min / max + wgt.gauge1.drawGauge(100, 180, 50, false, percentageValueMin, nil, nil, "", w_name) + wgt.gauge1.drawGauge(zone_w - 100, 180, 50, false, percentageValueMax, nil, nil, "", w_name) + lcd.drawText(50, 230, string.format("Min: %d%s", percentageValueMin, w_unit), MIDSIZE) + lcd.drawText(350, 230, string.format("Max: %d%s", percentageValueMax, w_unit), MIDSIZE) + +end + + +local function refresh_widget(wgt, w_name, value, minValue, maxValue, w_unit, percentageValue, percentageValueMin, percentageValueMax) + local w_name, value, minValue, maxValue, w_unit = getWidgetValue(wgt) + if (value == nil) then + return + end + + local percentageValue = getPercentageValue(value, wgt.options.Min, wgt.options.Max) + local percentageValueMin = getPercentageValue(minValue, wgt.options.Min, wgt.options.Max) + local percentageValueMax = getPercentageValue(maxValue, wgt.options.Min, wgt.options.Max) + + local value_fmt = "" + if wgt.options.precision == 0 then + value_fmt = string.format("%2.0f%s", value, w_unit) + else + value_fmt = string.format("%2.1f%s", value, w_unit) + end + + -- calculate low-profile or full-circle + local isFull = true + if wgt.zone.h < 60 then + lcd.drawText(wgt.zone.x + 10, wgt.zone.y, "too small for GaugeRotary", SMLSIZE + RED) + return + elseif wgt.zone.h < 90 then + log("widget too low (" .. wgt.zone.h .. ")") + if wgt.zone.w * 1.2 > wgt.zone.h then + log("wgt wider then height, use low profile ") + isFull = false + end + end + + local centerR, centerX, centerY + + if isFull then + centerR = math.min(wgt.zone.h, wgt.zone.w) / 2 + --local centerX = wgt.zone.x + (wgt.zone.w / 2) + centerX = wgt.zone.x + wgt.zone.w - centerR + centerY = wgt.zone.y + (wgt.zone.h / 2) + else + centerR = wgt.zone.h - 20 + centerX = wgt.zone.x + wgt.zone.w - centerR + centerY = wgt.zone.y + wgt.zone.h - 20 + end + + wgt.gauge1.drawGauge(centerX, centerY, centerR, isFull, percentageValue, percentageValueMin, percentageValueMax, value_fmt, w_name) + --lcd.drawText(wgt.zone.x, wgt.zone.y, value_fmt, XXLSIZE + YELLOW) + +end + + +local function refresh(wgt, event, touchState) + if (wgt == nil) then return end + if (wgt.options == nil) then return end + if (wgt.zone == nil) then return end + + --lcd.drawRectangle(wgt.zone.x, wgt.zone.y, wgt.zone.w, wgt.zone.h, BLACK) + + local ver, radio, maj, minor, rev, osname = getVersion() + --log("version: " .. ver) + if osname ~= "EdgeTX" then + local err = string.format("supported only on EdgeTX: ", osname) + log(err) + lcd.drawText(0, 0, err, SMLSIZE) + return + end + if maj == 2 and minor < 7 then + local err = string.format("NOT supported ver: %s", ver) + log(err) + lcd.drawText(0, 0, err, SMLSIZE) + return + end + + if (event ~= nil) then + -- full screen (app mode) + refresh_app_mode(wgt, event, touchState) + else + -- regular screen + refresh_widget(wgt) + end + + -- widget load (debugging) + lcd.drawText(wgt.zone.x + 10, wgt.zone.y, string.format("load: %d%%", getUsage()), SMLSIZE + GREY) -- ??? +end + +return { name = "GaugeRotary", options = _options, create = create, update = update, refresh = refresh } From cbf4c49426b8de703a7eec0284d3a56e6550c0c1 Mon Sep 17 00:00:00 2001 From: Shmuely Date: Sun, 1 May 2022 12:18:40 +0300 Subject: [PATCH 2/7] Rotary Gauge widget: less ticks on small size --- .../horus/WIDGETS/GaugeRotary/gauge_core.lua | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua index 7386f564..14bcd877 100644 --- a/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua +++ b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua @@ -101,6 +101,7 @@ function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, perc -- ticks local to_tick local tick_offset + local tick_step = 10 if isFull then to_tick = 210 tick_offset = 250 @@ -108,8 +109,10 @@ function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, perc to_tick = 210 tick_offset = 250 end - - for i = 0, to_tick, 10 do + if (centreR < 100) then + tick_step = 10 + 0.15 * (100 - centreR) + end + for i = 0, to_tick, tick_step do --log("HighAsGreen: " .. self.HighAsGreen) if (self.HighAsGreen == 1) then lcd.setColor(CUSTOM_COLOR, self.getRangeColor(i, 0, to_tick - 10)) @@ -147,17 +150,20 @@ function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, perc -- hide the base of the arm lcd.drawFilledCircle(centerX, centerY, armCenterR, BLACK) - - if isFull then - else - end + lcd.drawAnnulus(centerX, centerY, armCenterR-2, armCenterR, 0, 360, + --lcd.RGB(255, 255, 0) + lcd.RGB(192, 192, 192) + ) -- text in center lcd.drawText(centerX + 0, centerY - 8, txt2, CENTER + SMLSIZE + WHITE) -- XXLSIZE/DBLSIZE/MIDSIZE/SMLSIZE + + -- text below if isFull then - -- text below - lcd.drawText(centerX + 8, centerY + 30, txt1, CENTER + txtSize + WHITE) + --lcd.drawText(centerX + 8, centerY + 30, txt1, CENTER + txtSize + WHITE) + lcd.drawText(centerX + 0, centerY + armCenterR + 2, txt1, CENTER + txtSize + WHITE) else + -- no text below in flat mode end end From 4b6b8834b2bc4aa0a8b5ef9e390d057b3f66dbf5 Mon Sep 17 00:00:00 2001 From: Shmuely Date: Sun, 1 May 2022 12:23:39 +0300 Subject: [PATCH 3/7] new Rotary Gauge widget --- sdcard/horus/WIDGETS/GaugeRotary/main.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdcard/horus/WIDGETS/GaugeRotary/main.lua b/sdcard/horus/WIDGETS/GaugeRotary/main.lua index cf020c9c..fe9673b5 100644 --- a/sdcard/horus/WIDGETS/GaugeRotary/main.lua +++ b/sdcard/horus/WIDGETS/GaugeRotary/main.lua @@ -16,7 +16,7 @@ ---- # # ---- ######################################################################### --- This GaugeRotary widget display a fancy old style analog gauge with needle +-- This Rotary Gauge widget display a fancy old style analog gauge with needle -- Options: -- HighAsGreen: [checked] for sensor that high values is good (RSSI/Fuel/...) -- [checked] for sensor that high values is good (RSSI/Fuel/...) @@ -297,7 +297,7 @@ local function refresh(wgt, event, touchState) end -- widget load (debugging) - lcd.drawText(wgt.zone.x + 10, wgt.zone.y, string.format("load: %d%%", getUsage()), SMLSIZE + GREY) -- ??? +-- lcd.drawText(wgt.zone.x + 10, wgt.zone.y, string.format("load: %d%%", getUsage()), SMLSIZE + GREY) -- ??? end return { name = "GaugeRotary", options = _options, create = create, update = update, refresh = refresh } From 4da81b3f0ab82c94349d4c0e7addce80fb962c07 Mon Sep 17 00:00:00 2001 From: Shmuely Date: Fri, 6 May 2022 09:20:52 +0300 Subject: [PATCH 4/7] new Rotary Gauge widget --- sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua | 8 ++++---- sdcard/horus/WIDGETS/GaugeRotary/main.lua | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua index 14bcd877..09a02784 100644 --- a/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua +++ b/sdcard/horus/WIDGETS/GaugeRotary/gauge_core.lua @@ -129,11 +129,11 @@ function self.drawGauge(centerX, centerY, centreR, isFull, percentageValue, perc local armColor = lcd.RGB(255, 255, 255) local armColorMin, armColorMax if (self.HighAsGreen == 1) then - armColorMin = lcd.RGB(100, 0, 0) - armColorMax = lcd.RGB(0, 100, 0) + armColorMin = lcd.RGB(200, 0, 0) + armColorMax = lcd.RGB(0, 200, 0) else - armColorMin = lcd.RGB(0, 100, 0) - armColorMax = lcd.RGB(100, 0, 0) + armColorMin = lcd.RGB(0, 200, 0) + armColorMax = lcd.RGB(200, 0, 0) end --self.drawArm(centerX, centerY, armR, 0, armColorMin, isFull) diff --git a/sdcard/horus/WIDGETS/GaugeRotary/main.lua b/sdcard/horus/WIDGETS/GaugeRotary/main.lua index fe9673b5..a5e7232c 100644 --- a/sdcard/horus/WIDGETS/GaugeRotary/main.lua +++ b/sdcard/horus/WIDGETS/GaugeRotary/main.lua @@ -38,6 +38,9 @@ local UNIT_ID_TO_STRING = { "V", "A", "mA", "kts", "m/s", "f/s", "km/h", "mph", "m", "f", "°C", "°F", "%", "mAh", "W", "mW", "dB", "rpm", "g", "°", "rad", "ml", "fOz", "ml/m", "Hz", "uS", "km" } local DEFAULT_MIN_MAX = { {"RSSI" , 0, 100, 0}, + {"1RSS" , -120, 0, 0}, + {"2RSS" , -120, 0, 0}, + {"RQly" , 0, 100, 0}, {"RxBt" , 4, 10, 1}, {"TxBt" , 6, 8.4, 1}, {"Batt" , 6, 8.4, 1}, From ded5d5e733ee6b3e858daaabad9e8e573961d45d Mon Sep 17 00:00:00 2001 From: shmuely Date: Sat, 25 Nov 2023 23:13:53 +0200 Subject: [PATCH 5/7] libgui: support min/max on numbers libgui: update text offsets for 2.9 libgui: support scripts, and not just widgets --- sdcard/c480x272/WIDGETS/LibGUI/libgui.lua | 371 +++++++++++--------- sdcard/c480x272/WIDGETS/LibGUI/loadable.lua | 16 +- sdcard/c480x272/WIDGETS/LibGUI/main.lua | 13 +- 3 files changed, 220 insertions(+), 180 deletions(-) diff --git a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua index 570db63a..3fe9dbf0 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua @@ -2,8 +2,11 @@ -- The dynamically loadable part of the shared Lua GUI library. -- -- -- -- Author: Jesper Frickmann -- --- Date: 2022-11-20 -- --- Version: 1.0.2 -- +-- 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,18 +22,27 @@ -- GNU General Public License for more details. -- --------------------------------------------------------------------------- -local lib = { } +local app_ver = "1.0.3" + +local M = { } +M._ = {} -- internal members -function lib.getVer() - return "1.0.2" -end -- 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 = { +M.flags = 0 +M.colors = { primary1 = COLOR_THEME_PRIMARY1, primary2 = COLOR_THEME_PRIMARY2, primary3 = COLOR_THEME_PRIMARY3, @@ -39,8 +51,12 @@ lib.colors = { 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, ...) +function M.match(x, ...) for i, y in ipairs({ ... }) do if x == y then return true @@ -49,10 +65,9 @@ local function match(x, ...) return false end -lib.match = match -- Create a new GUI object with interactive screen elements -function lib.newGUI() +function M.newGUI() local gui = { x = 0, y = 0, @@ -65,6 +80,25 @@ function lib.newGUI() local scrolling = false local lastEvent = 0 + + function gui.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 @@ -116,7 +150,9 @@ function lib.newGUI() function gui.drawText(x, y, text, flags, inversColor) x, y = gui.translate(x, y) - lcd.drawText(x, y, text, flags, inversColor) + local ts_w, ts_h, v_offset = gui.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) @@ -160,8 +196,8 @@ function lib.newGUI() if #elements == 1 then return end - color = color or lib.colors.active - gui.drawRectangle(x - 2, y - 2, w + 4, h + 4, color, 2) + 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 @@ -219,27 +255,20 @@ function lib.newGUI() -- Show prompt function gui.showPrompt(prompt) - lib.prompt = prompt + M.prompt = prompt end -- Dismiss prompt function gui.dismissPrompt() - lib.prompt = nil + M.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.draw(false) + if event ~= nil then gui.onEvent(event, touchState) end lastEvent = event @@ -253,7 +282,7 @@ function lib.newGUI() end if focused then if gui.parent.editing then - drawFocus(0, 0, gui.w, gui.h, lib.colors.edit) + drawFocus(0, 0, gui.w, gui.h, M.colors.edit) else drawFocus(0, 0, gui.w, gui.h) end @@ -261,8 +290,8 @@ function lib.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.update then - element.update(element) + if element.onUpdate then + element.onUpdate(element) end if not element.hidden then element.draw(focus == idx and guiFocus) @@ -279,89 +308,97 @@ function lib.newGUI() return 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 + 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 -- non-zero event; process it - if not gui.parent or gui.parent.editing then - -- 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 + 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 - 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 + -- 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 - 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) - 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) + 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 - elseif event == EVT_VIRTUAL_ENTER then - gui.parent.editing = true end + elements[focus].onEvent(event, touchState) end end -- onEvent(...) @@ -371,8 +408,9 @@ function lib.newGUI() 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 + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), + disabled = true, + hidden= false, } function self.draw(focused) @@ -400,7 +438,7 @@ function lib.newGUI() function gui.labelLines(x, y, w, h, title, flags) local self = { title = title, - flags = bit32.bor(flags or flags, VCENTER, lib.colors.primary1), + flags = bit32.bor(flags or M.flags, VCENTER, M.colors.primary1), disabled = true, hidden= false, } @@ -431,7 +469,7 @@ function lib.newGUI() local self = { title = title, callBack = callBack or doNothing, - flags = bit32.bor(flags or lib.flags, CENTER, VCENTER), + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), disabled = false, hidden= false } @@ -441,8 +479,8 @@ function lib.newGUI() 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)) + 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) @@ -467,20 +505,20 @@ function lib.newGUI() title = title, value = value, callBack = callBack or doNothing, - flags = bit32.bor(flags or lib.flags, CENTER, VCENTER), + flags = bit32.bor(flags or M.flags, CENTER, VCENTER), disabled = false, hidden= false } function self.draw(focused) - local fg = lib.colors.primary2 - local bg = lib.colors.focus - local border = lib.colors.active + local fg = M.colors.primary2 + local bg = M.colors.focus + local border = M.colors.active if self.value then - fg = lib.colors.primary3 - bg = lib.colors.active - border = lib.colors.focus + fg = M.colors.primary3 + bg = M.colors.active + border = M.colors.focus end if focused then @@ -509,27 +547,30 @@ function lib.newGUI() ----------------------------------------------------------------------------------------------- -- Create a number that can be edited - function gui.number(x, y, w, h, value, onChangeValue, flags) + 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 lib.flags, VCENTER), + flags = bit32.bor(flags or M.flags, VCENTER), editable = true, disabled = false, - hidden= false + hidden= false, + min_val = min, + max_val = max } + local d0 function self.draw(focused) local flags = getFlags(self) - local fg = lib.colors.primary1 + local fg = M.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) + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) end end if type(self.value) == "string" then @@ -547,9 +588,13 @@ function lib.newGUI() self.value = value gui.editing = false elseif event == EVT_VIRTUAL_INC then - self.value = self.onChangeValue(1, self) + if self.value < self.max_val then + self.value = self.onChangeValue(1, self) + end elseif event == EVT_VIRTUAL_DEC then - self.value = self.onChangeValue(-1, self) + 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 @@ -577,7 +622,7 @@ function lib.newGUI() local self = { tmr = tmr, onChangeValue = onChangeValue or onChangeDefault, - flags = bit32.bor(flags or lib.flags, VCENTER), + flags = bit32.bor(flags or M.flags, VCENTER), disabled = false, hidden= false, editable = true @@ -587,7 +632,7 @@ function lib.newGUI() function self.draw(focused) local flags = getFlags(self) - local fg = lib.colors.primary1 + local fg = M.colors.primary1 -- self.value overrides the timer value local value = self.value or model.getTimer(self.tmr).value @@ -595,8 +640,8 @@ function lib.newGUI() drawFocus(x, y, w, h) if gui.editing then - fg = lib.colors.primary2 - gui.drawFilledRectangle(x, y, w, h, lib.colors.edit) + fg = M.colors.primary2 + gui.drawFilledRectangle(x, y, w, h, M.colors.edit) end end if type(value) == "string" then @@ -652,7 +697,7 @@ function lib.newGUI() 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), + flags = bit32.bor(flags or M.flags, VCENTER), disabled = false, hidden= false, editable = true, @@ -689,10 +734,10 @@ function lib.newGUI() local bgColor if focused and gui.editing then - bgColor = lib.colors.edit + bgColor = M.colors.edit else selected = self.selected - bgColor = lib.colors.focus + bgColor = M.colors.focus end for i = 0, visibleCount - 1 do @@ -701,9 +746,9 @@ function lib.newGUI() 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)) + 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(lib.colors.primary1, flags)) + gui.drawText(align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary1, flags)) end end @@ -716,7 +761,7 @@ function lib.newGUI() local visibleCount = math.min(visibleCount, #self.items) if moving ~= 0 then - if match(event, EVT_TOUCH_FIRST, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then + if M.match(event, EVT_TOUCH_FIRST, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then moving = 0 event = 0 else @@ -754,7 +799,7 @@ function lib.newGUI() if event == EVT_TOUCH_FIRST then scrolling = true firstVisibleScrolling = firstVisible - elseif match(event, EVT_VIRTUAL_NEXT, EVT_VIRTUAL_PREV) then + 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 @@ -790,12 +835,12 @@ function lib.newGUI() function gui.dropDown(x, y, w, h, items, selected, callBack, flags) callBack = callBack or doNothing - flags = flags or lib.flags + flags = flags or M.flags local self local showingMenu local drawingMenu - local dropDown = lib.newGUI() + 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 @@ -814,8 +859,8 @@ function lib.newGUI() 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) + 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 @@ -833,7 +878,7 @@ function lib.newGUI() drawingMenu = false drawMenu(focused) else - local flags = bit32.bor(VCENTER, lib.colors.primary1, getFlags(self)) + local flags = bit32.bor(VCENTER, M.colors.primary1, getFlags(self)) if focused then drawFocus(x, y, w, h) @@ -841,8 +886,8 @@ function lib.newGUI() 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) + 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 @@ -891,22 +936,22 @@ function lib.newGUI() 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 + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 if focused then - colorDotBorder = lib.colors.active + colorDotBorder = M.colors.active if gui.editing or scrolling then - colorBar = lib.colors.primary1 - colorDot = lib.colors.edit + colorBar = M.colors.primary1 + colorDot = M.colors.edit end end gui.drawFilledRectangle(x, y - 2, w, 5, colorBar) - gui.drawFilledCircle(xdot, y, SLIDER_DOT_RADIUS, colorDot) + gui.drawFilledCircle(xdot, y, M.SLIDER_DOT_RADIUS, colorDot) for i = -1, 1 do - gui.drawCircle(xdot, y, SLIDER_DOT_RADIUS + i, colorDotBorder) + gui.drawCircle(xdot, y, M.SLIDER_DOT_RADIUS + i, colorDotBorder) end end @@ -914,7 +959,7 @@ function lib.newGUI() local v0 = self.value if gui.editing then - if match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) 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) @@ -939,7 +984,7 @@ function lib.newGUI() 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) + return ((p - xdot) ^ 2 + (q - y) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) end addElement(self) @@ -963,22 +1008,22 @@ function lib.newGUI() 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 + local colorBar = M.colors.primary3 + local colorDot = M.colors.primary2 + local colorDotBorder = M.colors.primary3 if focused then - colorDotBorder = lib.colors.active + colorDotBorder = M.colors.active if gui.editing or scrolling then - colorBar = lib.colors.primary1 - colorDot = lib.colors.edit + colorBar = M.colors.primary1 + colorDot = M.colors.edit end end gui.drawFilledRectangle(x - 2, y, 5, h, colorBar) - gui.drawFilledCircle(x, ydot, SLIDER_DOT_RADIUS, colorDot) + gui.drawFilledCircle(x, ydot, M.SLIDER_DOT_RADIUS, colorDot) for i = -1, 1 do - gui.drawCircle(x, ydot, SLIDER_DOT_RADIUS + i, colorDotBorder) + gui.drawCircle(x, ydot, M.SLIDER_DOT_RADIUS + i, colorDotBorder) end end @@ -986,7 +1031,7 @@ function lib.newGUI() local v0 = self.value if gui.editing then - if match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) 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) @@ -1011,7 +1056,7 @@ function lib.newGUI() 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) + return ((p - x) ^ 2 + (q - ydot) ^ 2 <= 2 * M.SLIDER_DOT_RADIUS ^ 2) end addElement(self) @@ -1023,7 +1068,7 @@ function lib.newGUI() -- Create a custom element function gui.custom(self, x, y, w, h) self.gui = gui - self.lib = lib + 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) @@ -1054,7 +1099,7 @@ function lib.newGUI() -- Create a nested gui function gui.gui(x, y, w, h) - local self = lib.newGUI() + local self = M.newGUI() self.parent = gui self.editing = false self.x, self.y, self.w, self.h = x, y, w, h @@ -1072,4 +1117,4 @@ function lib.newGUI() end -- gui(...) -return lib +return M diff --git a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua index 5c415048..41dde0c4 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua @@ -41,9 +41,10 @@ local HEIGHT = 24 -- The widget table will be returned to the main script local widget = { } --- Load the GUI library by calling the global function declared in the main script. --- As long as LibGUI is on the SD card, any widget can call loadGUI() because it is global. -local libGUI = loadGUI() +-- Load the GUI library. +-- Note: for backward & forward compatibility, each script should come with it's own version of libgui. +local libGUI = loadScript("/WIDGETS/LibGUI/libgui.lua")() + -- Instantiate a new GUI object local gui = libGUI.newGUI() @@ -88,7 +89,7 @@ subGUI.number(COL2s, 0, WIDTH, HEIGHT, 0) -- A drop-down with physical switches subGUI.label(0, ROW, WIDTH, HEIGHT, "Drop-down:") -labelDropDown = subGUI.label(0, 2 * ROW, 2 * WIDTH, HEIGHT, "") +local labelDropDown = subGUI.label(0, 2 * ROW, 2 * WIDTH, HEIGHT, "") local dropDownIndices = { } local dropDownItems = { } @@ -216,7 +217,12 @@ end -- This function is called from the refresh(...) function in the main script function widget.refresh(event, touchState) - gui.run(event, touchState) + gui.run(event, touchState) + if event == nil then + lcd.drawFilledRectangle(0, 100-10, 480, 70, RED) + lcd.drawText(40, 100, "This script is running currently in \"widget\"mode") + lcd.drawText(40, 125, "It only work in app-mode (full screen)") + end end -- Return to the create(...) function in the main script diff --git a/sdcard/c480x272/WIDGETS/LibGUI/main.lua b/sdcard/c480x272/WIDGETS/LibGUI/main.lua index 6523e5a2..ee47694e 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/main.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/main.lua @@ -21,17 +21,6 @@ -- GNU General Public License for more details. -- --------------------------------------------------------------------------- local name = "LibGUI" -local libGUI - --- Return GUI library table -function loadGUI() - if not libGUI then - -- Loadable code chunk is called immediately and returns libGUI - libGUI = loadScript("/WIDGETS/" .. name .. "/libgui.lua") - end - - return libGUI() -end --------------------------------------------------------------------------- -- The following widget implementation demonstrates how to use the -- @@ -52,7 +41,7 @@ end local function background(widget) end -local options = { +local options = { } local function update(widget, options) From bf7180a49cd445dfb249511aefcc7784353a1388 Mon Sep 17 00:00:00 2001 From: shmuely Date: Wed, 6 Dec 2023 23:40:48 +0200 Subject: [PATCH 6/7] libgui: fix bug in min/max on numbers --- sdcard/c480x272/WIDGETS/LibGUI/libgui.lua | 136 ++++++++++---------- sdcard/c480x272/WIDGETS/LibGUI/loadable.lua | 2 +- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua index 3fe9dbf0..3a65b9f8 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/libgui.lua @@ -25,8 +25,6 @@ local app_ver = "1.0.3" local M = { } -M._ = {} -- internal members - -- Radius of slider dot M.SLIDER_DOT_RADIUS = 10 @@ -71,17 +69,18 @@ function M.newGUI() local gui = { x = 0, y = 0, - editable = true + editable = true, } - local handles = { } - local elements = { } - local focus = 1 - local scrolling = false - local lastEvent = 0 + local _ = {} -- internal members + _.handles = { } + _.elements = { } + _.focus = 1 + _.scrolling = false + _.lastEvent = 0 - function gui.lcdSizeTextFixed(txt, font_size) + function _.lcdSizeTextFixed(txt, font_size) local ts_w, ts_h = lcd.sizeText(txt, font_size) local v_offset = 0 @@ -150,7 +149,7 @@ function M.newGUI() function gui.drawText(x, y, text, flags, inversColor) x, y = gui.translate(x, y) - local ts_w, ts_h, v_offset = gui.lcdSizeTextFixed(text, M.FONT_SIZES.FONT_8) + 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 @@ -171,16 +170,16 @@ function M.newGUI() end -- The default callBack - local function doNothing() + function _.doNothing() end -- The default onChangeValue - local function onChangeDefault(delta, self) + function _.onChangeDefault(delta, self) return self.value + delta end -- Adjust text according to horizontal alignment - local function align(x, w, flags) + function _.align(x, w, flags) if bit32.band(flags, RIGHT) == RIGHT then return x + w elseif bit32.band(flags, CENTER) == CENTER then @@ -193,7 +192,7 @@ function M.newGUI() -- 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 + if #_.elements == 1 then return end color = color or M.colors.active @@ -204,14 +203,14 @@ function M.newGUI() 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 + _.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 + until not (_.elements[_.focus].disabled or _.elements[_.focus].hidden) or count > #_.elements end -- moveFocus(...) -- Moved the focused element @@ -221,10 +220,10 @@ function M.newGUI() 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 + local idx = _.focus + delta + if idx >= 1 and idx <= #_.elements then + _.elements[_.focus], _.elements[idx] = _.elements[idx], _.elements[_.focus] + _.focus = idx end end @@ -236,7 +235,7 @@ function M.newGUI() end end - elements[#elements+1] = element + _.elements[#_.elements+1] = element return element end -- addElement(...) @@ -250,7 +249,7 @@ function M.newGUI() -- Set an event handler function gui.setEventHandler(event, f) - handles[event] = f + _.handles[event] = f end -- Show prompt @@ -271,7 +270,7 @@ function M.newGUI() if event ~= nil then gui.onEvent(event, touchState) end - lastEvent = event + _.lastEvent = event end -- run(...) ----------------------------------------------------------------------------------------------- @@ -288,13 +287,13 @@ function M.newGUI() end end local guiFocus = not gui.parent or (focused and gui.parent.editing) - for idx, element in ipairs(elements) do + for idx, element in ipairs(_.elements) do -- Clients may provide an update function for elements if element.onUpdate then element.onUpdate(element) end if not element.hidden then - element.draw(focus == idx and guiFocus) + element.draw(_.focus == idx and guiFocus) end end end -- draw() @@ -303,7 +302,7 @@ function M.newGUI() function gui.onEvent(event, touchState) -- Make sure that focused element is active - if (elements[focus].disabled or elements[focus].hidden) then + if (_.elements[_.focus].disabled or _.elements[_.focus].hidden) then moveFocus(1) return end @@ -342,37 +341,37 @@ function M.newGUI() end -- ETX 2.8 rc 4 bug fix - if scrolling and event == EVT_VIRTUAL_ENTER_LONG then + 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 + 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 + 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 + 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 + _.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 + 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 @@ -382,7 +381,7 @@ function M.newGUI() 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) elseif event == EVT_VIRTUAL_PREV then @@ -390,15 +389,15 @@ function M.newGUI() elseif event == EVT_VIRTUAL_EXIT and gui.parent then gui.parent.editing = false else - if handles[event] then + if _.handles[event] then -- Is it being handled? Handler can modify event - event = handles[event](event, touchState) + 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) + _.elements[_.focus].onEvent(event, touchState) end end -- onEvent(...) @@ -415,7 +414,7 @@ function M.newGUI() function self.draw(focused) local flags = getFlags(self) - gui.drawText(align(x, w, flags), y + h / 2, self.title, flags) + gui.drawText(_.align(x, w, flags), y + h / 2, self.title, flags) end -- We should not ever onEvent, but just in case... @@ -445,7 +444,7 @@ function M.newGUI() function self.draw(focused) local flags = getFlags(self) - gui.drawTextLines(align(x, w, flags), y , w, h, self.title, flags) + gui.drawTextLines(_.align(x, w, flags), y , w, h, self.title, flags) end -- We should not ever onEvent, but just in case... @@ -468,7 +467,7 @@ function M.newGUI() function gui.button(x, y, w, h, title, callBack, flags) local self = { title = title, - callBack = callBack or doNothing, + callBack = callBack or _.doNothing, flags = bit32.bor(flags or M.flags, CENTER, VCENTER), disabled = false, hidden= false @@ -504,7 +503,7 @@ function M.newGUI() local self = { title = title, value = value, - callBack = callBack or doNothing, + callBack = callBack or _.doNothing, flags = bit32.bor(flags or M.flags, CENTER, VCENTER), disabled = false, hidden= false @@ -550,13 +549,13 @@ function M.newGUI() function gui.number(x, y, w, h, value, onChangeValue, flags, min, max) local self = { value = value, - onChangeValue = onChangeValue or onChangeDefault, + onChangeValue = onChangeValue or _.onChangeDefault, flags = bit32.bor(flags or M.flags, VCENTER), editable = true, disabled = false, hidden= false, - min_val = min, - max_val = max + min_val = min or 0, + max_val = max or 100, } local d0 @@ -574,9 +573,9 @@ function M.newGUI() end end if type(self.value) == "string" then - gui.drawText(align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) + 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)) + gui.drawNumber(_.align(x, w, flags), y + h / 2, self.value, bit32.bor(fg, flags)) end end @@ -621,7 +620,7 @@ function M.newGUI() function gui.timer(x, y, w, h, tmr, onChangeValue, flags) local self = { tmr = tmr, - onChangeValue = onChangeValue or onChangeDefault, + onChangeValue = onChangeValue or _.onChangeDefault, flags = bit32.bor(flags or M.flags, VCENTER), disabled = false, hidden= false, @@ -645,9 +644,9 @@ function M.newGUI() end end if type(value) == "string" then - gui.drawText(align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) + 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)) + gui.drawTimer(_.align(x, w, flags), y + h / 2, value, bit32.bor(fg, flags)) end end @@ -711,7 +710,7 @@ function M.newGUI() local visibleCount = math.floor(h / lh) local killEvt - callBack = callBack or doNothing + callBack = callBack or _.doNothing local function setFirstVisible(v) firstVisible = v @@ -746,9 +745,9 @@ function M.newGUI() 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)) + 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)) + gui.drawText(_.align(x, w, flags), y + lh / 2, self.items[j], bit32.bor(M.colors.primary1, flags)) end end @@ -784,7 +783,7 @@ function M.newGUI() end if event == EVT_TOUCH_SLIDE then - if scrolling then + if _.scrolling then if touchState.swipeUp then moving = 1 elseif touchState.swipeDown then @@ -794,10 +793,10 @@ function M.newGUI() end end else - scrolling = false + _.scrolling = false if event == EVT_TOUCH_FIRST then - scrolling = true + _.scrolling = true firstVisibleScrolling = firstVisible elseif M.match(event, EVT_VIRTUAL_NEXT, EVT_VIRTUAL_PREV) then if event == EVT_VIRTUAL_NEXT then @@ -834,7 +833,7 @@ function M.newGUI() ----------------------------------------------------------------------------------------------- function gui.dropDown(x, y, w, h, items, selected, callBack, flags) - callBack = callBack or doNothing + callBack = callBack or _.doNothing flags = flags or M.flags local self @@ -883,7 +882,7 @@ function M.newGUI() if focused then drawFocus(x, y, w, h) end - gui.drawText(align(x, w, flags), y + h / 2, self.items[self.selected], flags) + 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 @@ -927,7 +926,7 @@ function M.newGUI() min = min, max = max, delta = delta, - callBack = callBack or doNothing, + callBack = callBack or _.doNothing, disabled = false, hidden= false, editable = true @@ -942,7 +941,7 @@ function M.newGUI() if focused then colorDotBorder = M.colors.active - if gui.editing or scrolling then + if gui.editing or _.scrolling then colorBar = M.colors.primary1 colorDot = M.colors.edit end @@ -999,7 +998,7 @@ function M.newGUI() min = min, max = max, delta = delta, - callBack = callBack or doNothing, + callBack = callBack or _.doNothing, disabled = false, hidden= false, editable = true @@ -1014,7 +1013,7 @@ function M.newGUI() if focused then colorDotBorder = M.colors.active - if gui.editing or scrolling then + if gui.editing or _.scrolling then colorBar = M.colors.primary1 colorDot = M.colors.edit end @@ -1112,6 +1111,7 @@ function M.newGUI() return self end + ----------------------------------------------------------------------------------------------- return gui diff --git a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua index 41dde0c4..35e14fc2 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua @@ -85,7 +85,7 @@ local subGUI = gui.gui(COL2, TOP + ROW, COL4 + WIDTH - COL3, 2 * ROW + HEIGHT) -- A number that can be edited subGUI.label(0, 0, WIDTH, HEIGHT, "Number:") -subGUI.number(COL2s, 0, WIDTH, HEIGHT, 0) +subGUI.number(COL2s, 0, WIDTH, HEIGHT, 0, nil, nil, -10, 10) -- A drop-down with physical switches subGUI.label(0, ROW, WIDTH, HEIGHT, "Drop-down:") From 6a67a1f2760b21772005e2360a2c7e87d851c429 Mon Sep 17 00:00:00 2001 From: shmuely Date: Thu, 7 Dec 2023 10:04:48 +0200 Subject: [PATCH 7/7] libgui: fix no-operative widget text --- sdcard/c480x272/WIDGETS/LibGUI/loadable.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua index 35e14fc2..6c7ef181 100644 --- a/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua +++ b/sdcard/c480x272/WIDGETS/LibGUI/loadable.lua @@ -217,12 +217,16 @@ end -- This function is called from the refresh(...) function in the main script function widget.refresh(event, touchState) - gui.run(event, touchState) if event == nil then - lcd.drawFilledRectangle(0, 100-10, 480, 70, RED) - lcd.drawText(40, 100, "This script is running currently in \"widget\"mode") - lcd.drawText(40, 125, "It only work in app-mode (full screen)") + lcd.drawFilledRectangle(0, 0, LCD_W, HEADER, COLOR_THEME_SECONDARY1) + lcd.drawText(10, 40 / 2, "LibGUI Demo", VCENTER + MIDSIZE + libGUI.colors.primary2) + + lcd.drawFilledRectangle(0, 50-5, 480, 60, RED, 90) + lcd.drawText(10, 50, "change to full-screen") + lcd.drawText(10, 70, "to see the widget") + return end + gui.run(event, touchState) end -- Return to the create(...) function in the main script