Skip to content

widget-jet

Jet turbine dashboard using bitmap dials.

Install: copy the folder to SCRIPTS:/widget-jet/ on the radio (or the simulator's scripts/ folder) and restart.

main.lua
-- Lua FrkJet
---@diagnostic disable: undefined-global

-- Bitmap resources
local pumpPanel, pumpPointer, rpmPanel, rpmPointer, egtPanel, egtPointer, speedPanel, speedPointer

-- Values
local altValue = 0
local altUnit = ""

local fuelValue = 0

local rpmValue = 0
local rpmUnit = ""

local pumpValue = 0

local egtValue = 0
local egtUnit = ""

local speedValue = 0
local speedUnit = ""

local function calRotaryAngle(percent)
  local angle = 315 + percent * 270 / 100
  while angle > 360 do
    angle = angle - 360
  end
  return angle
end

local function calRotaryAngleWithLimit(min, percent, max)
  return calRotaryAngle((percent - min) * 100 / (max - min))
end

local function limit(min, value, max)
  return math.max(math.min(value, max), min)
end

local function create()
  if egtPanel == nil then
    pumpPanel = lcd.loadBitmap("pump_panel.png")
    pumpPointer = lcd.loadBitmap("pump_pointer.png")
    rpmPanel = lcd.loadBitmap("rpm_panel.png")
    rpmPointer = lcd.loadBitmap("rpm_pointer.png")
    egtPanel = lcd.loadBitmap("egt_panel.png")
    egtPointer = lcd.loadBitmap("egt_pointer.png")
    speedPanel = lcd.loadBitmap("speed_panel.png")
    speedPointer = lcd.loadBitmap("speed_pointer.png")
  end
  return { maxFuel = 100, maxRpm = 100, maxEgt = 100, maxSpeed = 100}
end

local function build(widget)
  local w, h = lcd.getWindowSize()
  if rpmPanel ~= nil then
    widget.rpmPanelLeft = w / 4 - rpmPanel:width() / 2
    widget.rpmPanelTop = h / 2 - rpmPanel:height() / 2
  end
  if rpmPointer ~= nil then
    widget.rpmPointerLeft = w / 4 - rpmPointer:width() / 2
    widget.rpmPointerTop = h / 2 - rpmPointer:height() / 2
  end

  if speedPanel ~= nil then
    widget.speedPanelLeft = w * 3 / 4 - speedPanel:width() / 2
    widget.speedPanelTop = h / 2 - speedPanel:height() / 2
  end
  if speedPointer ~= nil then
    widget.speedPointerLeft = w * 3 / 4 - speedPointer:width() / 2
    widget.speedPointerTop = h / 2 - speedPointer:height() / 2
  end

  if egtPanel ~= nil then
    widget.egtPanelLeft = w / 2 - egtPanel:width() / 2
    widget.egtPanelTop = h / 4 - egtPanel:height() / 2
  end
  if egtPointer ~= nil then
    widget.egtPointerLeft = w / 2 - egtPointer:width() / 2
    widget.egtPointerTop = h / 4 - egtPointer:height() / 2
  end

  if pumpPanel ~= nil then
    widget.pumpPanelLeft = w / 2 - pumpPanel:width() / 2
    widget.pumpPanelTop = h * 3 / 4 - pumpPanel:height() / 2
  end
  if pumpPointer ~= nil then
    widget.pumpPointerLeft = w / 2 - pumpPointer:width() / 2
    widget.pumpPointerTop = h * 3 / 4 - pumpPointer:height() / 2
  end

  widget.altArea = {x = 10, y = h / 2, h = h}
  widget.fuelArea = {x = w - 20, w = 20, h = h}
end

local PIXEL_PER_CURSOR = 20
local VALUE_PER_CURSOR = 2
local VALUE_PER_BIG_CURSOR = 10

local function paintAltitude(widget)
  lcd.font(FONT_S)
  local textW, textH = lcd.getTextSize(altValue)

  local ceilValue = math.ceil(altValue)
  local adj = ceilValue % VALUE_PER_CURSOR
  if adj ~= 0 then
    ceilValue = ceilValue + (VALUE_PER_CURSOR - adj)
  end
  local ceilY = widget.altArea.y - PIXEL_PER_CURSOR * (ceilValue - altValue) / VALUE_PER_CURSOR
  while ceilY > 0 do
    ceilY = ceilY - PIXEL_PER_CURSOR
    ceilValue = ceilValue + VALUE_PER_CURSOR
  end

  lcd.color(lcd.GREY(0xFF))
  while ceilY < widget.altArea.y * 2 do
    local isBigCursor = ceilValue % VALUE_PER_BIG_CURSOR == 0
    lcd.drawLine(0, ceilY, isBigCursor and widget.altArea.x or widget.altArea.x / 2, ceilY)
    if isBigCursor then
      lcd.drawText(widget.altArea.x + 5, ceilY - textH / 2, ceilValue)
    end
    ceilY = ceilY + PIXEL_PER_CURSOR
    ceilValue = ceilValue - VALUE_PER_CURSOR
  end

  lcd.color(lcd.GREY(0x00))
  local x = 0
  while x <= PIXEL_PER_CURSOR / 2 do
    lcd.drawLine(widget.altArea.x + x, widget.altArea.y - x, widget.altArea.x + x, widget.altArea.y + x)
    x = x + 1
  end
  lcd.drawFilledRectangle(widget.altArea.x + PIXEL_PER_CURSOR / 2, widget.altArea.y - PIXEL_PER_CURSOR / 2, textW + 10, PIXEL_PER_CURSOR)

  lcd.color(lcd.GREY(0xFF))
  lcd.drawLine(widget.altArea.x, widget.altArea.y, widget.altArea.x + PIXEL_PER_CURSOR / 2, widget.altArea.y - PIXEL_PER_CURSOR / 2)
  lcd.drawLine(widget.altArea.x + PIXEL_PER_CURSOR / 2, widget.altArea.y - PIXEL_PER_CURSOR / 2, widget.altArea.x + PIXEL_PER_CURSOR / 2 + textW + 10, widget.altArea.y - PIXEL_PER_CURSOR / 2)
  lcd.drawLine(widget.altArea.x, widget.altArea.y, widget.altArea.x + PIXEL_PER_CURSOR / 2, widget.altArea.y + PIXEL_PER_CURSOR / 2)
  lcd.drawLine(widget.altArea.x + PIXEL_PER_CURSOR / 2, widget.altArea.y + PIXEL_PER_CURSOR / 2, widget.altArea.x + PIXEL_PER_CURSOR / 2 + textW + 10, widget.altArea.y + PIXEL_PER_CURSOR / 2)
  lcd.drawLine(widget.altArea.x + PIXEL_PER_CURSOR / 2 + textW + 10, widget.altArea.y - PIXEL_PER_CURSOR / 2, widget.altArea.x + PIXEL_PER_CURSOR / 2 + textW + 10, widget.altArea.y + PIXEL_PER_CURSOR / 2)
  lcd.drawText(widget.altArea.x + PIXEL_PER_CURSOR / 2 + 5, widget.altArea.y - textH / 2, altValue)

  local maxW = lcd.getTextSize("-9999")
  lcd.drawText(widget.altArea.x + 5 + maxW, widget.altArea.h - textH * 2, altUnit)
  lcd.drawText(widget.altArea.x + 5 + maxW, widget.altArea.h - textH, "Altitude")
end

local function paintFuel(widget)
  lcd.color(lcd.GREY(0xFF))
  lcd.drawLine(widget.fuelArea.x, 0, widget.fuelArea.x + widget.fuelArea.w, 0)
  lcd.drawLine(widget.fuelArea.x, widget.fuelArea.h / 4, widget.fuelArea.x + widget.fuelArea.w, widget.fuelArea.h / 4)
  lcd.drawLine(widget.fuelArea.x, widget.fuelArea.h / 2, widget.fuelArea.x + widget.fuelArea.w, widget.fuelArea.h / 2)
  lcd.drawLine(widget.fuelArea.x, widget.fuelArea.h * 3 / 4, widget.fuelArea.x + widget.fuelArea.w, widget.fuelArea.h * 3 / 4)
  lcd.drawLine(widget.fuelArea.x, widget.fuelArea.h - 1, widget.fuelArea.x + widget.fuelArea.w, widget.fuelArea.h - 1)
  lcd.drawFilledRectangle(widget.fuelArea.x + widget.fuelArea.w - 3, 0, widget.fuelArea.x + widget.fuelArea.w, widget.fuelArea.h)

  local textW, textH = lcd.getTextSize("E")
  lcd.drawText(widget.fuelArea.x - textW, 0, "F", RIGHT)
  lcd.drawText(widget.fuelArea.x - textW, (widget.fuelArea.h - textH) / 2, "1/2", RIGHT)
  lcd.drawText(widget.fuelArea.x - textW, widget.fuelArea.h - textH, "E", RIGHT)

  local y = widget.fuelArea.h * (widget.maxFuel - fuelValue) / widget.maxFuel
  lcd.drawFilledRectangle(widget.fuelArea.x + widget.fuelArea.w / 2, y, widget.fuelArea.w / 2, widget.fuelArea.h - y)
end

local function paint(widget)
  local textW, textH = lcd.getTextSize(rpmValue)

  -- RPM
  if rpmPanel ~= nil and rpmPointer ~= nil then
    lcd.drawBitmap(widget.rpmPanelLeft, widget.rpmPanelTop, rpmPanel)
    lcd.color(lcd.GREY(0xFF))
    lcd.drawText(widget.rpmPanelLeft + rpmPanel:width() / 2, widget.rpmPanelTop + rpmPanel:height() / 2 + textH, rpmValue, CENTERED)
    lcd.drawText(widget.rpmPanelLeft + rpmPanel:width() / 2, widget.rpmPanelTop + rpmPanel:height() / 2 + textH * 2, rpmUnit, CENTERED)
    local angle = calRotaryAngleWithLimit(0, rpmValue, widget.maxRpm)
    lcd.drawBitmap(widget.rpmPointerLeft, widget.rpmPointerTop, rpmPointer:rotate(angle))
  end

  -- Speed
  if speedPanel ~= nil and speedPointer ~= nil then
    lcd.drawBitmap(widget.speedPanelLeft, widget.speedPanelTop, speedPanel)
    lcd.color(lcd.GREY(0xFF))
    lcd.drawText(widget.speedPanelLeft + speedPanel:width() / 2, widget.speedPanelTop + speedPanel:height() / 2 + textH, speedValue, CENTERED)
    lcd.drawText(widget.speedPanelLeft + speedPanel:width() / 2, widget.speedPanelTop + speedPanel:height() / 2 + textH * 2, speedUnit, CENTERED)
    local angle = calRotaryAngleWithLimit(0, speedValue, widget.maxSpeed)
    lcd.drawBitmap(widget.speedPointerLeft, widget.speedPointerTop, speedPointer:rotate(angle))
  end

  -- Egt
  if egtPanel ~= nil and egtPointer ~= nil then
    lcd.drawBitmap(widget.egtPanelLeft, widget.egtPanelTop, egtPanel)
    lcd.color(lcd.GREY(0xFF))
    lcd.drawText(widget.egtPanelLeft + egtPanel:width() / 2, widget.egtPanelTop + egtPanel:height() / 2 + textH, math.floor(egtValue) .. egtUnit, CENTERED)
    local angle = calRotaryAngleWithLimit(0, egtValue, widget.maxEgt)
    lcd.drawBitmap(widget.egtPointerLeft, widget.egtPointerTop, egtPointer:rotate(angle))
  end

  -- Pump
  if pumpPanel ~= nil and pumpPointer ~= nil then
    lcd.drawBitmap(widget.pumpPanelLeft, widget.pumpPanelTop, pumpPanel)
    lcd.color(lcd.GREY(0xFF))
    lcd.drawText(widget.pumpPanelLeft + pumpPanel:width() / 2, widget.pumpPanelTop + pumpPanel:height() / 2 + textH, math.floor(pumpValue) .. "%", CENTERED)
    local angle = calRotaryAngleWithLimit(0, pumpValue, 100)
    lcd.drawBitmap(widget.pumpPointerLeft, widget.pumpPointerTop, pumpPointer:rotate(angle))
  end

  -- Altitude
  paintAltitude(widget)

  -- Fuel
  paintFuel(widget)
end

local function wakeup(widget)
  local invalidateNeeded = false
  if widget.altSource ~= nil then
    local newValue = widget.altSource:value()
    if altValue ~= newValue then
      altValue = newValue
      invalidateNeeded = true
    end
    local newUnit = widget.altSource:stringUnit() or ""
    if altUnit ~= newUnit then
      altUnit = newUnit
      invalidateNeeded = true
    end
  end

  if widget.rpmSource ~= nil and widget.maxRpm ~= nil then
    local newValue = widget.rpmSource:value()
    newValue = limit(0, newValue, widget.maxRpm)
    if rpmValue ~= newValue then
      rpmValue = newValue
      invalidateNeeded = true
    end
    local newUnit = widget.rpmSource:stringUnit() or ""
    if rpmUnit ~= newUnit then
      rpmUnit = newUnit
      invalidateNeeded = true
    end
  end

  if widget.speedSource ~= nil and widget.maxSpeed ~= nil then
    local newValue = widget.speedSource:value()
    newValue = limit(0, newValue, widget.maxSpeed)
    if speedValue ~= newValue then
      speedValue = newValue
      invalidateNeeded = true
    end
    local newUnit = widget.speedSource:stringUnit() or ""
    if speedUnit ~= newUnit then
      speedUnit = newUnit
      invalidateNeeded = true
    end
  end

  if widget.fuelSource ~= nil and widget.maxFuel ~= nil then
    local newValue = widget.fuelSource:value()
    newValue = limit(0, newValue, widget.maxFuel)
    if fuelValue ~= newValue then
      fuelValue = newValue
      invalidateNeeded = true
    end
  end

  if widget.pumpSource ~= nil then
    local newValue = widget.pumpSource:value()
    newValue = limit(0, newValue, 100)
    if pumpValue ~= newValue then
      pumpValue = newValue
      invalidateNeeded = true
    end
  end

  if widget.egtSource ~= nil and widget.maxEgt ~= nil then
    local newValue = widget.egtSource:value()
    newValue = limit(0, newValue, widget.maxEgt)
    if egtValue ~= newValue then
      egtValue = newValue
      invalidateNeeded = true
    end
    local newUnit = widget.egtSource:stringUnit() or ""
    if egtUnit ~= newUnit then
      egtUnit = newUnit
      invalidateNeeded = true
    end
  end

  if invalidateNeeded then
    lcd.invalidate()
  end
end

local function configure(widget)
  local line = form.addLine("Altitude source")
  form.addSourceField(line, nil, function() return widget.altSource end, function(newValue) widget.altSource = newValue end)

  local panel = form.addExpansionPanel("RPM configure")
  local rpmMaxEdit
  line = panel:addLine("RPM source")
  form.addSourceField(line, nil, function() return widget.rpmSource end, function(newValue)
    widget.rpmSource = newValue
    if rpmMaxEdit ~= nil and widget.rpmSource ~= nil then
      rpmMaxEdit:suffix(widget.rpmSource:stringUnit())
    end
  end)
  line = panel:addLine("Max RPM")
  rpmMaxEdit = form.addNumberField(line, nil, 0, 10000, function() return widget.maxRpm end, function(newValue) widget.maxRpm = newValue end)
  if widget.rpmSource ~= nil then
    rpmMaxEdit:suffix(widget.rpmSource:stringUnit())
  end

  panel = form.addExpansionPanel("Speed configure")
  local speedMaxEdit
  line = panel:addLine("Speed source")
  form.addSourceField(line, nil, function() return widget.speedSource end, function(newValue)
    widget.speedSource = newValue
    if speedMaxEdit ~= nil and widget.speedSource ~= nil then
      speedMaxEdit:suffix(widget.speedSource:stringUnit())
    end
  end)
  line = panel:addLine("Max Speed")
  speedMaxEdit = form.addNumberField(line, nil, 0, 10000, function() return widget.maxSpeed end, function(newValue) widget.maxSpeed = newValue end)
  if widget.speedSource ~= nil then
    speedMaxEdit:suffix(widget.speedSource:stringUnit())
  end

  panel = form.addExpansionPanel("Fuel configure")
  local maxFuelEdit
  line = panel:addLine("Fuel source")
  form.addSourceField(line, nil, function() return widget.fuelSource end, function(newValue)
    widget.fuelSource = newValue
    if maxFuelEdit ~= nil and widget.fuelSource ~= nil then
      maxFuelEdit:suffix(widget.fuelSource:stringUnit())
    end
  end)
  line = panel:addLine("Max fuel")
  maxFuelEdit = form.addNumberField(line, nil, 0, 10000, function() return widget.maxFuel end, function(newValue) widget.maxFuel = newValue end)
  if widget.fuelSource ~= nil then
    maxFuelEdit:suffix(widget.fuelSource:stringUnit())
  end

  panel = form.addExpansionPanel("EGT configure")
  local egtMaxEdit
  line = panel:addLine("EGT source")
  form.addSourceField(line, nil, function() return widget.egtSource end, function(newValue)
    widget.egtSource = newValue
    if egtMaxEdit ~= nil and widget.egtSource ~= nil then
      egtMaxEdit:suffix(widget.egtSource:stringUnit())
    end
  end)
  line = panel:addLine("Max EGT")
  egtMaxEdit = form.addNumberField(line, nil, 0, 10000, function() return widget.maxEgt end, function(newValue) widget.maxEgt = newValue end)
  if widget.egtSource ~= nil then
    maxFuelEdit:suffix(widget.egtSource:stringUnit())
  end

  line = form.addLine("Pump source")
  form.addSourceField(line, nil, function() return widget.pumpSource end, function(newValue) widget.pumpSource = newValue end)
end

local function read(widget)
  widget.altSource = storage.read("altSource")

  widget.rpmSource = storage.read("rpmSource")
  widget.maxRpm = storage.read("maxRpm")

  widget.speedSource = storage.read("speedSource")
  widget.maxSpeed = storage.read("maxSpeed")

  widget.fuelSource = storage.read("fuelSource")
  widget.maxFuel = storage.read("maxFuel")

  widget.egtSource = storage.read("egtSource")
  widget.maxEgt = storage.read("maxEgt")

  widget.pumpSource = storage.read("pumpSource")
end

local function write(widget)
  storage.write("altSource", widget.altSource)

  storage.write("rpmSource", widget.rpmSource)
  storage.write("maxRpm", widget.maxRpm)

  storage.write("speedSource", widget.speedSource)
  storage.write("maxSpeed", widget.maxSpeed)

  storage.write("fuelSource", widget.fuelSource)
  storage.write("maxFuel", widget.maxFuel)

  storage.write("egtSource", widget.egtSource)
  storage.write("maxEgt", widget.maxEgt)

  storage.write("pumpSource", widget.pumpSource)
end

local function init()
  system.registerWidget({ key = "FrkJet", name = "Jet dashboard", create = create, build = build, paint = paint, wakeup = wakeup, configure = configure, read = read, write = write, title = false })
end

return { init = init }

Assets

egt_panel.png egt_pointer.png pump_panel.png pump_pointer.png rpm_panel.png rpm_pointer.png speed_panel.png speed_pointer.png