Dynamic Menus for apps/widgets?

Former Member
Former Member
Hi all,

I've been developing a very simple Connect IQ widget for my Fenix 3 - it's a timer that lets you set the timer length, as well as to set a number of repeats for that timer (e.g., 1 minute for 30 iterations). I've run into a few problems that I've developed kludgy work-arounds for but I'm curious if others have any ideas. Primarily they revolve around menus:

From the main screen of my app, pressing the Menu button brings up an options menu that lets you change the timer length, number of repeats, and vibration/tone preferences. One big question I had is - when a user navigates to the Menu, then chooses for example to alter the timer length (via a NumberPicker) - when they are brought back to the Options Menu, I'd like to update the displayed menu to show the new length

For example, if when they first came to the Menu, it showed the following 3 items:

- "1:00"
- "30 times"
- "Vibration only"

and then they change the length to 2:00, I'd like the Menu to display as follows when the NumberPicker bounces them back to the Menu (and similarly for the other Menu options that can be changed)

- "2:00"
- "30 times"
- "Vibration only"

What I'm currently doing is, in the various delegates (e.g., the NumberPickerDelegate), I pop the current view (the old menu) and push the a new Menu with the updated settings. It works but one very minor issue is that it then brings me back up to the top entry in the Menu every time (whereas on the stock Garmin menus, if you select form a NumberPicker and press Enter, when you go back, your original item is selected vs. the top item in the menu).

Is there any way to get the Menus to behave like the stock Garmin menus (a good example is the stock Timer app - when you change any settings it brings you back to the Menu with the original item selected AND the Menu item's display updated to reflect your changes).

Also - some Garmin menus display a two line display with the top line bolded and the next line unbolded e.g.,:

Notification
Vibration

Is there any way to currently achieve the same effect?

Lastly - unrelated to Menus - is there a generic NumberPicker that just lets you select an integer from 1 to X? None of the WatchUi.NUMBER_PICKER_ options seem to offer what I wanted (I thought the Calorie one might be a work around but on the Fenix 3 it increments by 50 up or down).

Thanks for the help.
  • I put in a request for a Generic number picker a while back. I ended up writing my own to set the high and low SmO2 alert limits. Here's the code.

    I don't know how big the memory hit there is to a homemade number picker versus a native one. Same with the menus. I haven't taken the time to test this out myself. I'm up against the memory limit with my App now so maybe a generic number picker would help.

    using Toybox.WatchUi as Ui;
    using Toybox.Application as App;
    using Toybox.Graphics as Gfx;
    using Toybox.System as Sys;

    class AlertPicker extends Ui.View {
    var setPoint;
    var mPersist;
    var text;
    var mHilo;
    var width=0;
    var height=0;

    function initialize(hilo, persist)
    {
    mPersist = persist;
    mHilo = hilo;
    if (mHilo.equals("Hi")){
    text = "Set High SmO2 Alert";
    }
    if (mHilo.equals("Lo")){
    text = "Set Low SmO2 Alert";
    }
    }

    function onUpdate(dc)
    {
    width = dc.getWidth();
    height = dc.getHeight();
    var txtFont = Gfx.FONT_MEDIUM;
    var txtHeight = dc.getFontHeight(txtFont);
    var numFont = Gfx.FONT_NUMBER_HOT;
    var numHeight = dc.getFontHeight(numFont);
    var numWidth = dc.getTextWidthInPixels("00", numFont);
    var margin = 10;
    var numCenter = height/2 + txtHeight/2;
    dc.setColor( Gfx.COLOR_TRANSPARENT, Gfx.COLOR_BLACK );
    dc.clear();
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
    dc.drawText(width / 2, margin, txtFont, text, Gfx.TEXT_JUSTIFY_CENTER);

    if (mHilo.equals("Hi")){
    setPoint = mPersist.GetAlrtSpHi();
    }
    if (mHilo.equals("Lo")){
    setPoint = mPersist.GetAlrtSpLo();
    }
    var rectTop = numCenter - numHeight / 2 - margin / 2;
    dc.drawRectangle((width - numWidth - margin) / 2, rectTop, numWidth + margin, numHeight + margin);
    dc.drawRectangle((width - numWidth - margin) / 2 - 1, rectTop - 1, numWidth + margin+2, numHeight + margin+2);
    var polygon = [ [width/2,rectTop-11], [width/2-8,rectTop-3], [width/2+8,rectTop-3] ];
    dc.fillPolygon(polygon);
    polygon = [ [width/2,rectTop+numHeight + margin+11], [width/2-8,rectTop+numHeight + margin+3], [width/2+8,rectTop+numHeight + margin+3] ];
    dc.fillPolygon(polygon);
    dc.drawText(width / 2, numCenter - numHeight / 2, numFont, setPoint.format("%d"), Gfx.TEXT_JUSTIFY_CENTER);
    polygon = [ [0+width-50,0+numCenter], [5+width-50,-5+numCenter], [13+width-50,3+numCenter], [31+width-50,-15+numCenter], [36+width-50,-10+numCenter], [13+width-50,13+numCenter] ];
    dc.setColor(Gfx.COLOR_GREEN, Gfx.COLOR_TRANSPARENT);
    dc.fillPolygon(polygon);
    }
    }

    class AlertPickerDelegate extends Ui.InputDelegate {
    var mHilo;
    var mPersist;
    var mView;
    var setPoint;
    var max;
    var min;

    function initialize(hilo, persist, view)
    {
    mHilo = hilo;
    mPersist = persist;
    mView = view;
    if (mHilo.equals("Hi"))
    {
    setPoint = mPersist.GetAlrtSpHi();
    max = 100;
    min = mPersist.GetAlrtSpLo() + 1;
    }
    else if (mHilo.equals("Lo"))
    {
    setPoint = mPersist.GetAlrtSpLo();
    max = mPersist.GetAlrtSpHi() - 1;
    min = 0;
    }
    Ui.requestUpdate();
    }

    function onKey(evt)
    {
    if (evt.getKey() == Ui.KEY_DOWN)
    {
    increment(-1);
    }
    else if (evt.getKey() == Ui.KEY_UP)
    {
    increment(1);
    }
    else if (evt.getKey() == Ui.KEY_ENTER)
    {
    Ui.popView(Ui.SLIDE_IMMEDIATE);
    }
    else if (evt.getKey() == Ui.KEY_ESC)
    {
    Ui.popView(Ui.SLIDE_IMMEDIATE);
    }
    return true;
    }

    function onTap(evt)
    {
    var location = evt.getCoordinates();
    var width = mView.width;
    var height = mView.height;
    if (location[0] > 2*width/3)
    {
    Ui.popView(Ui.SLIDE_IMMEDIATE);
    }
    if (location[0] < 2*width/3 && location[1] < height/2)
    {
    increment(1);
    }
    if (location[0] < 2*width/3 && location[1] > height/2)
    {
    increment(-1);
    }
    return true;
    }

    function increment(inc)
    {
    setPoint = setPoint + inc;
    if (setPoint >= max)
    {
    setPoint = max;
    }
    if (setPoint <= min)
    {
    setPoint = min;
    }
    if (mHilo.equals("Hi"))
    {
    mPersist.SetAlrtSpHi(setPoint);
    }
    else if (mHilo.equals("Lo"))
    {
    mPersist.SetAlrtSpLo(setPoint);
    }
    Ui.requestUpdate();
    }
    }
  • Former Member
    Former Member
    MoxyRoger - thanks so much for the reply (and the code). I actually did the same after posting - rolled my own "generic" NumberPicker + delegate (code posted below if it's helpful for anyone). Now I guess I'll have to do the same with the menus if I want to have the app really behave nicely from a user perspective. Hopefully future versions of the Connect IQ sdk will give us access to what the stock software is using with regards to the fancier menu behavior, etc.

    (Attaching because apparently it's a few characters too long to paste)

  • I have a working implementation a menu that does this already. I can provide it when I get home tonight.

    Travis
  • Former Member
    Former Member
    You can download the source here.


    Thanks so much for sharing this Travis.
  • TRAVIS.VITEK - Would you mind sharing your menu again?
  • I have the same problem, can you please share it again?

  • Much has changed since this thread started  Look at using Menu2 instead of Menu.