Menu2 assit

Hello,

I am trying to figure out how to do on watch Menus.  It’s gotten the better of me for a while now.  So…I tried to extract how to do a menu based on ComplexFW (thanks again JM).  The only thing I want it to do is display a time and use an on-watch menu to determine if it displays 24 hour or 12 hour time.

The watch face runs until I select “trigger app settings” in the simulator.  Then, nothing happens until about 5 seconds later when the whole thing crashes.  There are no errors listed in the Problems, Debug console, or Terminal.  It just shows the garmin triangle and stops running.

I’ve posted the entire (short?) program here: https://github.com/FlyFrosty/MyMenu

Any help would be appreciated

Jeff

  • Is this the latest source? You've defined myMenuSettingsMenu and myMenuMenuDelegate, but there's no reference to them in the rest of the app.

    The app class also doesn't override getSettingsView(), which is what needs to happen for on-device settings to work. As such, "trigger app settings" is unavailable for me when I run your code. (So I can't recreate your exact problem)

    I took a guess and added getSettingsView() as follows:

    function getSettingsView() as [Views] or [Views, InputDelegates] or Null {
        var menu = new myMenuSettingsMenu();
        return [menu, new myMenuMenuDelegate(menu)];
    }

    Seems to work fine for me. When I trigger app settings in the sim, the settings menu is displayed, I can change the setting, and nothing crashes.

    There are other problems in the code, such as:

    - the global leadZero value is not updated when settings change, which means the new setting is never used by the main watchface view

    - the main watchface view doesn't use the user's configured time format (12 vs 24 hour)

    - the main view assumes that if leadZero is true, the user wants 24-hour time (this may or may not be a reasonable assumption)

    I realize it's just an unfinished proof of concept tho.

  • My apologies for the rambling code.  I’ve reached the throw “stuff" at the fan stage and see if anything works.  I actually don’t care what happens when the item is selected...I just want to see the menu display.  

    Where did you put that function? I dropped it in several places without effect.  I thought I was missing a pushView, so i wasn’t even close.

    Thanks again,

    novice Jeff

  • If you look at the complexWF sample, with trigger app you get a menu with one item ."leading zero for hours" and look at getSettiingView and what it returns including for the delegate. You can see how the menu2 is built and how to interact with it.

  • I’ve reached the throw “stuff" at the fan stage and see if anything works.  I actually don’t care what happens when the item is selected...I just want to see the menu display.  

    Yeah I figured, thought I'd mention that stuff just in case.

    Where did you put that function?

    Sorry I wasn't clear about that. I meant to imply it with: "The app class also doesn't override getSettingsView()"

    In general, you put getSettingsView() in your app class which extends AppBase. In your specific case, put it in myMenuApp. Note that what I called the "app class" is also known as the entry class (it's the class that's invoked when your app is launched), as seen in manifest.xml:

    <iq:application ... entry="myMenuApp" ...>

    You don't need to push the settings view because when on-device settings is triggered in the sim or on the real device, the firmware / Garmin code calls getSettingsView() in your app and pushes whatever view that function returns.

    It's similar to how getInitialView() returns the initial view, and it's handled by the firmware - there's no need for your app to explicitly push/switch to the initial view, either.

  • Ok...  I started over and now have a program that only does 1 Menu2 thing (changes the clock color).  It works…after the first toggle.  I am not sure why…but I’ve been staring at it too long to see where I am failing to load.  Anyways, if it helps, the program is located here...

    https://github.com/FlyFrosty/Menu-Example

    Thanks for the guidance and patience,

    Jeff

  • [1/4]

    TL;DR you have to initialize the global var menuSelector (which determines the actual colour) to match the value of the persisted setting "mySelection" (which determines the toggle state), otherwise the toggle and the actual colour will get out of sync when the setting is changed and the app is closed, then reopened.

    The easy fix is to change newTryView as follows:

        var menuSelector;
    
    class newTryView extends WatchUi.WatchFace {
    
        var wWidth=200;
        var wHeight=200;
    
        function initialize() {
            WatchFace.initialize();
            
            menuSelector = Application.Storage.getValue("mySelection"); // <==== new code
        }

    Obviously this leads to duplicated code, so you probably want to look at refactoring your app so settings are only loaded and saved in one place.

  • [2/4]

    Couple of observations:

    1) It is not a good idea to have an on/off toggle that is supposed to switch between blue and red

    It is impossible for the end user to know which toggle state (on/off) is supposed to correspond with which colour (blue or red), only by looking at the toggle and the text. It does seem that in your app, off = red and on = blue.

    Yes, many designers abuse toggles to select between two different things (as opposed to enabling or disabling one thing), but they usually make this visually clear.

    e.g.

    Blue  Red

    Blue   Red

    I would argue this is still a bad idea even if it makes sense visually [*]. It isn't really possible out-of-the-box in Monkey C (thankfully).

    In your case, it would make more sense to have a menu item whose label is Color and whose subLabel indicates the current color. When the user selects the Color item, it opens a 2nd menu which allows the user to select the actual colour. This is how Garmin built-in settings tend to work. The other advantage of this scheme is that it can support more than 2 colours.

    2) "It works…after the first toggle."

    That's because you're not loading the persisted setting when the app runs (before the user opens the settings menu).

    Imagine what happens the first time your app is opened:

    - The menuSelector global variable is initialized to null, so the default colour will be red (as null is falsey). Every time your app is opened, the clock will be red, regardless of the actual persisted setting.

    - Now if you open settings, on first run, there's no persisted "mySelection" value, so your toggle will default to off (which matches the clock colour of red). If you change the toggle to on, the "mySelection" value will be persisted as true.

    - If you subsequently close the app and re-open it, menuSelector (global var) will once again be initialized to null and the actual colour will be red. But if you open the settings menu, then the toggle is initialized from "mySelection" (property/setting), and it will "on" (which indicates blue). Now the actual colour and the toggle state are out of sync. If you change the toggle to off (meaning red), the actual colour will remain unchanged because it was already red. If you change the toggle to off, the actual colour will change to blue, as expected

    3) It's very confusing to have a global var named menuSelector which controls the actual colour, while the toggle for changing the colour is tied to a setting called mySelection. In an ideal world, you would just call these things by the same descriptive name (like "watchFaceColor" or "clockColor" or something).

    4) By convention, class names are capitalized, so it's easy to tell the difference between classes and non-classes. This also lets you you use a variable name for a class instance that's the same as class name, except for the capitalization.

    e.g.

    class FancyAppView {};

    var fancyAppView = new FancyAppView();

    In general:

    - methods and variables start with a lowercase letter, and use camelCase to distinguish words

    - class names and type names start with an uppercase letter, and use CamelCase to distinguish words

    - enum names / constants are all uppercase (words are separated by underscores): e.g. CONSTELLATION_GPS

    5) If you ever intend on *also* using "off-device settings" (which are configured in the Connect app), you'll probably want to share persisted values between on-device settings and off-device settings. e.g. if you change the color on-device, you'll want that to be reflected in off-device settings as well, and vice versa.

    If so, you should be using Application.Properties, not Application.Storage, because only properties can be associated with off-device settings, not storage.

  • [3/4]

    > even if it makes sense visually [*]. (abusing toggles for something other than on/off states)

    [*] I would argue it doesn't, since the on state of a toggle is usually a vibrant colour like green, while the off state is a neutral colour like grey, which makes sense for indicating a on/off state, but doesn't really make sense for choosing between 2 neutral states.

    Note that normal on/off toggles will have the label on the left side of the toggle in L-to-R languages like English, and as far as the toggle switches go: left = "off" and right = on". Therefore when you turn a normal toggle "on", the toggle switch is on the *opposite* side of the "selected" label. But when you have a toggle that selects between two labels on either side of the toggle, the the toggle switch is on the *same* side as the "selected" label.

  • [4/4]

    IOW:

    Standard usage of toggles

    - this means "GPS" is on:

    GPS 

    (the toggle switch is on the opposite side of the "active" label, and it's colorful)

    - this means "GPS" is off:

    GPS 

    (the toggle switch is on the same side of the "inactive" label, and it's grey)


    Non-standard usage of toggles

    this means the selected value is "GPS"

    GPS  GLONASS

    (the toggle switch is on the *same* side of the "active" label, and it's grey)

    - and this means the selected value is "GLONASS"

    GPS  GLONASS

    (the toggle switch is on the *same* side of the "active" label, and it's colorful)

    At the very least, both the "left" and "right" states of the toggle should be same colour here, but very few designers/coders will implement this (because standard toggles don't work that way).

    It would be better to implement this with radio buttons (although those are kinda passe) or button groups (you have two buttons, one of them is "selected", and clicking on the other button selects that button and de-selects the 1st button)


    it's just not consistent imo.

  • Thanks for all the feedback. I agree, the toggle is horrible for what I am using it for.  However…

    1) toggle appeared as the easiest to use (both creation and use),  

    2) Garmin makes it stupid hard to find al related documentation for items.  All the items dealing with Menu2 (delegates required, types of menu options…)should be under Menu2.  This may just be me.

    and 3) I just wanted a basic menu program that I could expand and learn from without 10 other things happening around it,  This app has no usefulness past that.  Once I understand better (tweak and crash and revive several times) what is happening, I can incorporate it into my watch faces (downloaded by tens of people :D)  

    FWIW, I’ve already removed tons of “extra” functions that Garmin included with a basic watch face and have fixed the launch issue (all hail dinner breaks).

    The key was figuring out how the delegates and calls work (from your first response). So, thanks again for the poke in the right direction.

    Jeff