Good scriptable lockscreen widget tutorial and resource?

Yes, using DrawContext, .strokeEllipse etc.

Here’s a reimplementation of the circular range widget (left) with the temperature widget (right). Transparent masking doesn’t seem possible with DrawContext so that would require another approach.

Screenshot 2022-11-22 at 7.35.21 pm

function circularRange(val, min, max, width, height) {
  let context = new DrawContext()
  context.size = new Size(width, height)
  context.respectScreenScale = true
  context.opaque = false
  
  // background
  let rect = new Rect(0, 0, width, height)
  context.setFillColor(Color.black())
  context.fillRect(rect)

  // ring
  let lineWidth = 16
  context.setLineWidth(lineWidth)
  let ring = new Rect(lineWidth/2, lineWidth/2, width - lineWidth, height - lineWidth)
  context.setStrokeColor(Color.gray())
  context.strokeEllipse(ring)

  // mask
  let path = new Path()
  path.move(new Point(width / 2, height / 2))
  let end = Math.PI / 6
  let my = ((1 + Math.tan(end)) / 2) * height
  path.addLine(new Point(0, my))
  path.addLine(new Point(0, height))
  path.addLine(new Point(width, height))
  path.addLine(new Point(width, my))
  context.addPath(path)
  context.setFillColor(Color.black())
  context.fillPath()

  // rounded ends
  let lx = ((1 - Math.cos(end)) / 2) * (width - lineWidth)
  let ly = ((1 + Math.sin(end)) / 2) * (height - lineWidth)
  let lend = new Rect(lx, ly, lineWidth, lineWidth)
  let rx = ((1 + Math.cos(end)) / 2) * (width - lineWidth)
  let rend = new Rect(rx, ly, lineWidth, lineWidth)
  context.setFillColor(Color.gray())
  context.fillEllipse(lend)
  context.fillEllipse(rend)

  // value
  let a = (195 - (val - min) / (max - min) * 210) * Math.PI / 180
  let vx = ((1 + Math.cos(a)) / 2) * (width - lineWidth)
  let vy = ((1 - Math.sin(a)) / 2) * (height - lineWidth)
  let value = new Rect(vx, vy, lineWidth, lineWidth)
  context.setLineWidth(lineWidth * 3/4)
  context.setStrokeColor(Color.black())
  context.strokeEllipse(value)
  context.setFillColor(Color.white())
  context.fillEllipse(value)

  // labels
  context.setTextColor(Color.white())
  context.setFont(Font.mediumSystemFont(72))
  context.setTextAlignedCenter()
  context.drawTextInRect(val.toString(), new Rect(width/4, height/4, width/2, height/2))
  context.setFont(Font.regularSystemFont(32))
  context.setTextAlignedLeft()
  context.drawTextInRect(min.toString(), new Rect(width/5, height * 7/10, width * 3/10, height * 3/10))
  context.setTextAlignedRight()
  context.drawTextInRect(max.toString(), new Rect(width/2, height * 7/10, width * 3/10, height * 3/10))

  return context
}

let widget = new ListWidget()
widget.setPadding(0, 0, 0, -8)
let image = circularRange(16, 6, 18, 175, 175).getImage()
widget.addImage(image)
if (config.runsInAccessoryWidget) {
  Script.setWidget(widget)
} else {
  widget.presentSmall()
}