More than one draw content in a datafield?

I wondered if it's possible to have more than one Dc (draw content) in a datafield.
Sometimes it's good to devide the screen part of a field (the Dc) in smaller parts. So each part could be filled and drawn independent.

For my datafield it would also be nice to stack one Dc on top of another.
Example:
Activity summary is shown graphically in background and current data ist shown on top.
The background don't need to be drawn again on every onUpdate call. It could drawn on the dc every 10 sec or later in activity every 1 minute (to preserve processor time and battery).
So I thought about putting a (already drawn) dc in background and a dc with transparent background on top. So a background dc could be reused without redrawing all graphic content and a foreground dc could be shown on top updated every second.

It's not possible yet with functionality of complex datafield class. But I'm interested if this could be useful for others.
What is Garmin thinking about it? Could that be possible (technical in SDK-framework) in future? Thanks for a comment.
  • not sure ensitely of your question hence not sure if this is what you're looking for.

    currently the way DC works, you can paint or repaint the entire screen either in 1 sec updates and some developers have even managed to update only parts of the screen using some methods. (I also do this in a crude way)

    if you want to do a Foreground and a background and you are saying you only want the BG to be updated every 10sec I think it's do-able in a sense.
    The dc works like layers in a photoshop palette. You can do your data as 1 layer and your BG as another layer, and then use some counter to update the BG every 10sec but the rest of the watchface every sec.

    perhaps this is not what you want?
  • You could easily create a class to wrap a Dc and limit the draw area by reporting the dimensions of the area it should draw into. I'm not sure it would be useful, but you could do it.

    The Dc in ConnectIQ doesn't appear to store the drawn data, it simply acts as an interface for doing draw operations. It seems that the output is written to the frame buffer directly. For your idea of overlaying drawn data, that would require some temporary storage to draw into (similar to a memory Dc in Windows GDI speak), and then functionality to copy that data to the frame buffer (like BitBlt). Most of the devices don't have enough memory available to provide such a memory buffer. Even with a 16 color palette, a 205x148 screen requires ~14.8K of memory. A data field only gets 16K, and I believe that more than 2K is taken up by the system... You'd have exceeded the memory limits before you had added any program logic. Not only that, but to display the pre-rendered data on the frame buffer would require copying that 14.8K of data from memory to the frame buffer, which isn't going to be cheap either (I'm not sure what would be more costly, but these devices don't really scream accelerated graphics).

    Several people have asked for this stuff, so clearly Garmin knows people want it. They may have already had many discussions on the topic, but until the devices get more resources, I have serious doubts you'll see any changes in this area.

    Note: I'm not an expert on this stuff, so I could be wrong. I don't believe that I am, but I definitely could be.

    Travis
  • Hello NIKEOW and TRAVIS, thanks for your comments.

    @Nikeow: Your example is what I want to do. But does it work? Dc has no layers (like Travis wrote). Do you have a example code?

    @Travis: It sound realistic to me and I think there is actually no chance to have layers in a dc.
    But I can't figure out how your suggestion of areas in a dc should work?
    My idea was to create 2 dcs inside a datafield (redefinition of dc class), put them into a area (so each has its own dimension readable with dc.getWidth) and call them to update/draw the content. But the dc is given to the datafield-view from outside in onUpdate().
    How can I put own dcs on this one? Or do you mean I should just call some draw handler and pass the dc and the area as parameter and the handler should only draw inside the area?
    Do you have a small example code for yor idea? Thanks.
  • I don't exactly do partial redraws or such, but I've done something (as an app - 64kb. not a datafield tho) that basically covers up the background.

    eg: in my GymTimer app, I have an interval timer that shows work and rest time, but during the initial start, I want to provide users a pre-workout "get ready" countdown. So, what I do is just draw a rectangle covering up the work/rest timers and then use that layer to draw on top of it.

    of course what i'm doing here is simple thing, which need the BG colour to be simple. in this case, White BG, Black Numbers. I draw a white rectangle on top of the Black numbers creating a white canvas of sort and then I write my "get ready" on top of it.

    I still wanted some parts of the original interface to show and not only show the entire screen as "get ready"
  • I'm working on a DF that does a bunch of drawing in onUpdate(), and figured that I'd try ignoring every other call to reduce battery usage.

    Turns out before the call to onUpdate(), the dc is cleared to the background color, so doing this had the effect that the data field "blinked" (every other second it was blank.)

    So unlike an app, with a DF, you can't just do a partial draw, as the dc is cleared outside of your control.
  • But I can't figure out how your suggestion of areas in a dc should work? My idea was to create 2 dcs inside a datafield (redefinition of dc class), put them into a area (so each has its own dimension readable with dc.getWidth) and call them to update/draw the content. But the dc is given to the datafield-view from outside in onUpdate().

    Yes, this is one way you could go about it. It sounds like this is what you are wanting to do...

    // looks and smells like a Graphics.Dc. you can pass it to any function that expects
    // one, and it will behave the same, except the draw area will be different. note that this
    // does nothing to prevent drawing outside of the given area.
    class MyDc
    {
    hidden var _dc;
    hidden var _x;
    hidden var _y;
    hidden var _w;
    hidden var _h;

    function initialize(x, y, w, h) {
    _x = x;
    _y = y;
    _w = w;
    _h = h;
    }

    function setDc(dc) {
    _dc = dc;
    }

    function setLocation(x, y) {
    _x = x;
    _y = y;
    }

    function setSize(w, h) {
    _w = w;
    _h = h;
    }

    function clear() {
    return _dc.fillRectangle(_x, _y, _w, _h);
    }

    function drawArc(x, y, r, attr, degreeStart, degreeEnd) {
    return _dc.drawArc(_x + x, _y + y, attr, degreeStart, degreeEnd);
    }

    function drawBitmap(x, y, rez) {
    return _dc.drawBitmap(_x + x, _y + y, rez);
    }

    function drawCircle(x, y, radius) {
    return _dc.drawCircle(_x + x, _y + y, radius);
    }

    function drawEllipse(x, y, a, b) {
    return _dc.drawEllipse(_x + x, _y + y, a, b);
    }

    function drawLine(x1, y1, x2, y2) {
    return _dc.drawLine(_x + x1, _y + y1, _x + x2, _y + y2);
    }

    function drawPoint(x, y) {
    return _dc.drawPoint(_x + x, _y + y);
    }

    function drawRectangle(x, y, width, height) {
    return _dc.drawRectangle(_x + x, _y + y, width, height);
    }

    function drawRoundedRectangle(x, y, width, height, radius) {
    return _dc.drawRoundedRectangle(_x + x, _y + y, width, height, radius);
    }

    function drawText(x, y, font, text, justification) {
    return _dc.drawText(_x + x, _y + y, font, text, justification);
    }

    function fillCircle(x, y, radius) {
    return _dc.fillCircle(_x + x, _y + y, radius);
    }

    function fillEllipse(x, y, a, b) {
    return _dc.fillEllipse(_x + x, _y + y, a, b);
    }

    function fillPolygon(pts) {

    // we must either modify the input data or make a copy and modify that.
    // for compatibility we make a copy. ugh.

    var new_pts = new [ pts.size() ];

    for (var i = 0; i < new_pts.size(); ++i) {
    new_pts= [ _x + pts[0], _y + pts[1] ];
    }

    return _dc.fillPolygon(new_pts);
    }

    function fillRectangle(x, y, width, height) {
    return _dc.fillRectangle(_x + x, _y + y, width, height);
    }

    function fillRoundedRectangle(x, y, width, height, radius) {
    return _dc.fillRoundedRectangle(_x + x, _y + y, width, height, radius);
    }

    function getFontHeight(font) {
    return _dc.getFontHeight(font);
    }

    function getHeight() {
    return _h;
    }

    function getTextDimensions(text, font) {
    return _dc.getTextDimensions(text, font);
    }

    function getTextWidthInPixels(text, font) {
    return _dc.getTextWidthInPixels(text, font);
    }

    function getWidth() {
    return _w;
    }

    function setColor(foreground, background) {
    return _dc.setColor(foreground, background);
    }

    function setPenWidth(width) {
    return _dc.setPenWidth(width);
    }
    }

    class MyDataField extends Ui.DataField
    {
    hidden var _dc_top;
    hidden var _dc_bot;

    function initialize() {
    DataField.initialize();
    }

    function onLayout(dc) {
    _dc_top = null;
    _dc_bot = null;

    var w = dc.getWidth();
    var h = dc.getHeight();

    _dc_top = new MyDc(0, 0, w, h / 2);
    _dc_bot = new MyDc(0, h / 2, w, h - h / 2);
    }

    function onUpdate(dc) {
    _dc_top.setDc(dc);
    // draw in the top dc
    _dc_top.setColor(Gfx.COLOR_RED, Gfx.COLOR_RED);
    _dc_top.clear();

    _dc_bot.setDc(dc);
    // draw in the bottom dc
    _dc_top.setColor(Gfx.COLOR_BLUE, Gfx.COLOR_BLUE);
    _dc_top.clear();
    }
    }
    [/code]

    If I were writing something like this, I'd not do it that way. I think a better idea would be to use drawables that only draw when it is necessary. For instance, if you are implementing an game that has a progress bar that changes very infrequently. This class would only update the progress bar when the value changed or it is forced to redraw by marking it as dirty.

    class MyProgressBar extends Ui.Drawable
    {
    hidden var _fgcolor;
    hidden var _bgcolor;
    hidden var _border;

    hidden var _progress; // Lang.Number 0...100
    hidden var _dirty; // indicates we need to redraw

    function initialize(params) {
    Drawable.initialize(params);

    _fore_color = params["fore_color"];
    if (_fore_color == null) {
    _fore_color = Gfx.COLOR_RED;
    }

    _back_color = params["back_color"];
    if (_back_color == null) {
    _back_color = Gfx.COLOR_BLACK;
    }

    _edge_color = params["edge_color"];
    if (_edge_color == null) {
    _edge_color = Gfx.COLOR_WHITE;
    }

    _progress = 0;
    _dirty = true;
    }

    // force a redraw
    function setDirty() {
    _dirty = true;
    }

    // `percent' is expected to be in the range of 0...1
    function setProgress(percent) {
    // convert to number in range 0...100 so we can compare against
    // previous value without fear of failed comparison due to floating
    // point issues
    var progress = (percent * 100).toNumber();
    if (progress != _progress) {
    _progress = progress;
    setDirty();
    }
    }

    function draw(dc) {
    if (_dirty) {
    // clear the draw area so we don't see artifacts of previous draw
    _dc.setColor(_back_color, _back_color);
    _dc.fillRectangle(locX, locY, width, height);

    _dc.setColor(_fore_color, _fore_color);
    _dc.fillRectangle(locX, locY, (width * _progress) / 100, height);

    _dc.setColor(_edge_color, _edge_color);
    _dc.drawRectangle(locX, locY, width, height);

    _dirty = false;
    }
    }
    }


    This is really nice because you can get device-specific placement of your drawable as provided by the layout system, and you avoid redrawing parts of the screen automatically with rules that are defined by each derived drawable.

    Of course for data fields, you are subject to the issue mentioned by Jim above and you'll probably be forced to redraw all of your data on every frame.

    Travis
  • Thanks to all.

    Thanks Travis for the example. You had to copy many functiosn for this :D

    Your second example. I keep it in mind if I need something like this.
    I experimented with drawables defined in ressource file and changed in datafield. But this took so much memory that the memory was gone after some drawables without further code.
    Drawables have only a advantage against drawing on the dc inside areas which are only covered by the drawable, right? Drawing on top it need again a redraw of all.
  • I experimented with drawables defined in ressource file and changed in datafield. But this took so much memory that the memory was gone after some drawables without further code.

    Yes, using layouts with datafields doesn't work very well right now for a few reasons. The first being that there are many datafield configurations, so you need many layouts. The second is that the layout definitions take up tons of memory. :(

    Drawables have only a advantage against drawing on the dc inside areas which are only covered by the drawable, right?

    You're asking if the draw area is clipped by the drawable? No, I don't believe that this is the case.

    Drawing on top it need again a redraw of all.

    Sort of. If you want to draw over something, you just need to make sure that you are drawing over all of the stale data. In some cases this doesn't mean you need to redraw all, you just need to redraw the stuff from the previous frame. In my example above, I draw over the entire progress bar area in black (the background color) before drawing the fill area and border. That way if the progress decreases you'll still see it.

    Provided you don't overlap drawables, the drawable system I proposed above should work quite well. If you start overlapping things, it will get very complicated.