Good scriptable lockscreen widget tutorial and resource?

Thanks man, really appreciate it! Good to know that making lockscreen widgets is pretty similar to making regular widgets. :grin:

Anyone know if it is possible to create those circles that temperature and fitness are using?
image

I’m no expert in making graphical stuff with scriptable but I see others doing it by making the graphic in a <canvas> element inside a WebView then exporting it from there.

1 Like

Since last week I saw we can add Scriptables in the Lockscreen in the date box. (See my screenshot) Who has used that already? Any examples? :thinking:

So glad I found your post, thanks. I was trying to find changes to the Scriptable docs with the release of lockscreen widget support, and it seems the online docs are not updated (and missing addAccessoryWidgetBackground) while the in-app docs are.

@simonbs if there’s any version history for docs that can be made publicly available that would be much appreciated :pray:


For anyone else interested, I believe these are the only lockscreen-widget-specific changes, all additions to ListWidget:

addAccessoryWidgetBackground: boolean;
presentAccessoryCircular: () => Promise<void>;
presentAccessoryInline: () => Promise<void>;
presentAccessoryRectangular: () => Promise<void>;

I may have missed something though.

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()
}

A couple more

  • config.runsInAccessoryWidget
  • config.widgetFamily adds 3 more values - accessoryRectangular, accessoryInline, and accessoryCircular
1 Like

Thank you for all answers. I have now done a few Lock Screen widgets and will share them here
flopp999/Scriptable (github.com)

Hello,
Please, could someone explain to me the values ​​195 and 210. It seems that the restitution of the temperature is not always correct
Thanks

These are angles in degrees for positioning the value dot on the arc proportionally between the min and max values. The arc is 210 degrees long and starts at 195 degrees.

2 Likes

Thanks a lot for your explanations !

2 Likes

The widget mode that appears above the clock (is it called “inline”?) will automatically prefix the widget with `Weekday Day" (e.g., “Sun 22”). How do I disable this? I don’t need this info, and it takes precious space.

1 Like

I don’t think it can be disabled. It’s the same with other widgets, e.g. Things.

Hello,

Strangely, the Color.clear() (on the background ‘s rect) is OK with circular widget but not the rectangular one! See screenshot with your widget and mine.
Perhaps, something to do for Simon?

Yes, I would say so. I think the next Scriptable update would be 2.0, with a lot of new stuff to make use of, e.g. SwiftUI.

1 Like

Any idea when that would be? Its been a while now.

Probably a while yet. Here’s a post from October. Simon is surprisingly productive with these hobby projects considering everything else he does (day job, conferences).

2 Likes

Can Someone help me? I want the widget to show on LockScreen but when I press add new widget there is no Option to select Scriptable in list. Appreciate every hint and help.

Welcome! If I understand correctly, you’ve got Scriptable installed but it’s not appearing as an option to add when you customise the Lock Screen? I haven’t seen this myself but I think I remember reading about someone else seeing this and fixing it by restarting their phone so maybe try that first.

Hey, no i wrote the Script myself. I got a hint that i should put this in my Code:
widget.presentAccessoryRectangular()
And then I could choose from the List Scriptable. It would be nice if the docs would say something about it.

A different question. Someone told me that Scriptable refresh randomly itself and runs my code. Is there a good way to prevent this? So it refreshes only once a day? For example in the morgning at 7am.

And what does this do exactly: refreshAfterDate