dc.getHeight() behaves differently on different devices (simulator)

I'm trying to figure out a good way to place things on the screen, but the dc.getHeight() confuses me. My code is like this:

function initialize() {
View.initialize();
var time = App.getApp().getProperty("period_duration");
var sec = time%60;
var min = (time/60);
App.getApp().setProperty("menu_time_min", min);
App.getApp().setProperty("menu_time_sec", sec);
App.getApp().setProperty("menu_time_pos", 1);
}

// Load your resources here
// function onLayout(dc) {
// setLayout(Rez.Layouts.MainLayout(dc));
// }

// Called when this View is brought to the foreground. Restore
// the state of this View and prepare it to be shown. This includes
// loading resources into memory.
function onShow() {
}

// Update the view
function onUpdate(dc) {
// Call the parent onUpdate function to redraw the layout
View.onUpdate(dc);
var x = dc.getWidth()/2;
var y = percentInPx(15, dc.getHeight());
var numFont = Graphics.FONT_NUMBER_MEDIUM;
var text = "Set time";
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
dc.drawText(x, y, Graphics.FONT_MEDIUM, text, Graphics.TEXT_JUSTIFY_CENTER);

// Up-/downarrows
var triSize = 16;
y = percentInPx(32, dc.getHeight());
dc.fillPolygon([[x, y], [x+triSize, y+triSize], [x-triSize, y+triSize]]);
y = percentInPx(80, dc.getHeight());
dc.fillPolygon([[x, y+triSize], [x+triSize, y], [x-triSize, y]]);

// Draw time
y = percentInPx(50, dc.getHeight());
var sec = App.getApp().getProperty("menu_time_sec"); //time%60;
var min = App.getApp().getProperty("menu_time_min");//(time/60);
text = padLeft(min) + ":" + padLeft(sec);
var offset = dc.getTextWidthInPixels("00:", numFont);
var startOffset = dc.getTextWidthInPixels("00", numFont);
var shift = startOffset / 2 + offset*(App.getApp().getProperty("menu_time_pos")-1);
dc.drawText(x-shift, y, numFont, text, Graphics.TEXT_JUSTIFY_LEFT);
}

// Called when this View is removed from the screen. Save the
// state of this View here. This includes freeing resources from
// memory.
function onHide() {
}

function percentInPx(percent, total) {
return total / 100 * percent;
}

function padLeft(s) {
return s.toString().length() == 1 ? "0"+s.toString() : s.toString();
}
I thought this code would give me a semi-nice behavior on various devices but when I try different ones, the result varies a lot. I thought that at least the "time" would be rendered at the middle of my screen, but that's not the case. Look at the screenshots from a Fenix 7s, Fenix 5xPlus and FR230, respectively.

Is it the simulator that is erroneous or am I missing something in the implementation?
  • You're assuming percentInPx(50, dc.getHeight()) will always return 50% of dc.getHeight() but that isn't the case.

    You can see this by adding some debug printlns to your code:

        function percentInPx(percent, total) {
            System.println("percent = " + percent);
            System.println("total = " + total);
            var scaledAmount = total / 100 * percent;
            System.println("scaledAmount = " + scaledAmount);
            return scaledAmount;
        }
    

    Output for fr235:

    percent = 50
    total = 180
    scaledAmount = 50

    What's happening here is that total and 100 are both Numbers, so dividing total by 100 yields a Number (the answer is truncated, meaning it's rounded down to the nearest integer.)

    This can be fixed by changing total / 100 to total / 100.0, which results in the following output:

    percent = 50
    total = 180
    scaledAmount = 90.000000

    The various drawing calls like drawText will accept non-integer coordinates although of course they will round convert them to integers. If you want to ensure the return value is always an integer, either:

    - change return scaledAmount to return scaledAmount.toNumber() (always rounds down)

    or

    - change return scaledAmount to return Math.round(scaledAmount) (rounds to the nearest integer)

  • Thanks for the explanation! Forgot that division of Numbers had that effect. Now it all makes sense!

  • I use floats for graphics, but I've never known for sure if the VM rounds or truncates so I always round first. Do we know for a fact that Graphics rounds vs truncates by testing or confirmation by Garmin?

  • This is easy to try to find out.  Consider this code

    dc.setColor(Graphics.COLOR_RED, Graphics.COLOR_TRANSPARENT);
    dc.drawLine(0,100,width,100);
    dc.setColor(Graphics.COLOR_BLUE, Graphics.COLOR_TRANSPARENT);
    dc.drawLine(0,100.9,width,100.9);

    In both the sim and on a real device, all you see is a single blue line.

    if you change both 100.9s to 101 in the second drawLine, you see both the red and blue line

  • So Graphics truncates and does not round, so the rounding I'm doing myself is not redundant.

  • It kind of depends if you round up or down.  What does your rounding say for 100.9?

    You can probably avoid the cost of doing rounding by just doing "100.9+1" in the second drawLine. but that really depends on your code.

  • So Graphics truncates and does not round, so the rounding I'm doing myself is not redundant.

    It kind of depends if you round up or down.  What does your rounding say for 100.9?

    You can probably avoid the cost of doing rounding by just doing "100.9+1" in the second drawLine. but that really depends on your code.

    From context he quite obviously means "round to the nearest integer", in which case simply adding 1 doesn't help. Even if he means "round up", simply adding 1 doesn't help unless you know the value in advance (as in your contrived example of "100.9+1".) Of course if you knew the value in advance, you wouldn't need a rounding strategy in your code, you could just round by hand.

    Consider the following broken implementation of roundUp(), as per your suggestion:

    function roundUp(val as Lang.Numeric) as Lang.Number {
      return (val + 1).toNumber();
    }

    roundUp(1.1) => 2 White check mark

    roundUp(2) => 3 X

    In contrast, a working implementation (for all non-negative numbers) would look like this:

    function roundUp(val as Lang.Numeric) as Lang.Number {
      return Math.ceil(val);
    }

  • I'm gonna guess you probably *meant* to type "+ 0.5" though. (EDIT: adding 0.5 then truncating should be equivalent to rounding to the nearest integer.)

    That would also work if you always wanted to round up, and it would save the cost of calling Math.ceil():

    function roundUp(val as Lang.Numeric) as Lang.Number {
      return (val + 0.5).toNumber();
    }

    But I still doubt that's what he meant.

  • It's a real gift to be a mind reader and know what people like  andy was saying with out asking  questions!  Can you also levitate?  Slight smile

  • Kids, play nice. I use Math.round() because that seems like the simplest and most logical choice. I'm not a fan of reinventing wheels. Rounding doesn't need to be hard. I have found that laying out the graphics is much easier if I assume that the dc dimensions are 0.0..1.0, then scale to the pixel dimensions when time to do the actual drawing functions. This is why how Graphics treats floats and why rounding before calling the drawing functions are important.

    Adding 0.5 myself before I call dc.drawsomething might be beneficial if we knew what the added cost to using floats as parameters instead of numbers is since there seems to be a conversion to numbers in the functions anyway.