• Joel Martin's avatar
    Viewport clip/drag for mobile/touchscreen devices. · a5df24b4
    Joel Martin authored
    API changes (forward compatible):
    
    - Display: add 'viewport' conf option to turn on and off viewport
      mode.
    - RFB: add 'viewportDrag' option to enable/disable viewport dragging
      mode.
    
    Other:
    
    - Add clip mode setting to default UI. For touch devices, clipping is
      forced on.
    - Use CSS media queries to adjust visual elements based on screen
      size. Especially disconnected logo size/position and button text size.
    - Catch page unload while connected and give a confirm dialog.
    - Change mouse button selector to a single button that changes between
      ' ', 'L', 'M', 'R' when clicked (empty means mouse is just being
      moved and doesn't send clicks).
    - include/ui.js:setViewClip() routine sets the clipping of the
      viewport to the current size of the viewport area (if clipping is
      enabled).
    - include/ui.js:setViewDrag() toggles/enables/disables viewport
      dragging mode.
    - Add several images for the UI and for Apple devices:
        - images/clipboard.png: clipboard menu icon
        - images/connect.png: connect menu icon
        - images/disconnect.png: disconnect button icon
        - images/keyboard.png: show keyboard button
        - images/move.png: viewport drag/move toggle button
        - images/settings.png: settings menu icon
        - images/screen_320x460.png: iOS app/desktop link start image
        - images/screen_57x57.png: iOS app icon
        - images/screen_700x700.png: full size noVNC image
    a5df24b4
viewport.html 6.48 KB
<!DOCTYPE html>
<html>
    <head><title>Viewport Test</title>
        <link rel="stylesheet" href="viewport.css">
        <!--
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
        -->
        <meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=no">
    </head>
    <body>
        <div class="flex-layout">
            <div>
                Canvas:
                    <input id="move-selector" type="button" value="Move"
                        onclick="toggleMove();">
                <br>
            </div>
            <div class="container flex-box">
                <canvas id="canvas" class="canvas">
                    Canvas not supported.
                </canvas>
                <br>
            </div>
            <div>
                <br>
                Results:<br>
                <textarea id="messages" style="font-size: 9;" cols=80 rows=8></textarea>
            </div>
        </div>
    </body>

    <!--
    <script type='text/javascript' 
        src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
    -->
    <script src="../include/util.js"></script>
    <script src="../include/webutil.js"></script> 
    <script src="../include/base64.js"></script>
    <script src="../include/input.js"></script> 
    <script src="../include/display.js"></script>
    <script>
        var msg_cnt = 0, iterations,
            penDown = false, doMove = false,
            inMove = false, lastPos = {},
            padW = 0, padH = 0,
            display, ctx, keyboard, mouse;

        var newline = "\n";
        if (Util.Engine.trident) {
            var newline = "<br>\n";
        }

        function message(str) {
            console.log(str);
            cell = $D('messages');
            cell.innerHTML += msg_cnt + ": " + str + newline;
            cell.scrollTop = cell.scrollHeight;
            msg_cnt++;
        }

        function mouseButton(x, y, down, bmask) {
            //msg = 'mouse x,y: ' + x + ',' + y + '  down: ' + down;
            //msg += ' bmask: ' + bmask;
            //message(msg);

            if (doMove) {
                if (down && !inMove) {
                    inMove = true;
                    lastPos = {'x': x, 'y': y};
                } else if (!down && inMove) {
                    inMove = false;
                    //dirtyRedraw();
                }
                return;
            }

            if (down && ! penDown) {
                penDown = true;
                ctx.beginPath();
                ctx.moveTo(x, y);
            } else if (!down && penDown) {
                penDown = false;
                ctx.closePath();
            }
        }

        function mouseMove(x, y) {
            var deltaX, deltaY;

            if (inMove) {
                //deltaX = x - lastPos.x; // drag viewport
                deltaX = lastPos.x - x; // drag frame buffer
                //deltaY = y - lastPos.y; // drag viewport
                deltaY = lastPos.y - y; // drag frame buffer
                lastPos = {'x': x, 'y': y};

                display.viewportChange(deltaX, deltaY);
                return;
            }

            if (penDown) {
                ctx.lineTo(x, y);
                ctx.stroke();
            }
        }

        function dirtyRedraw() {
            if (inMove) {
                // Wait for user to stop moving viewport
                return;
            }

            var d = display.getCleanDirtyReset();

            for (i = 0; i < d.dirtyBoxes.length; i++) {
                //showBox(d.dirtyBoxes[i], "dirty[" + i + "]: ");
                drawArea(d.dirtyBoxes[i]);
            }
        }

        function drawArea(b) {
            var data = [], pixel, x, y;

            //message("draw "+b.x+","+b.y+" ("+b.w+","+b.h+")");

            for (var i = 0; i < b.w; i++) {
                x = b.x + i;
                for (var j = 0; j < b.h; j++) {
                    y = b.y + j;
                    pixel = (j * b.w * 4 + i * 4);
                    data[pixel + 0] = ((x * y) / 13) % 256;
                    data[pixel + 1] = ((x * y) + 392) % 256;
                    data[pixel + 2] = ((x + y) + 256) % 256;
                    data[pixel + 3] = 255;
                }
            }
            //message("i: " + i + ", j: " + j + ", pixel: " + pixel);
            display.blitImage(b.x, b.y, b.w, b.h, data, 0);
        }

        function toggleMove() {
            if (doMove) {
                doMove = false;
                $D('move-selector').style.backgroundColor = "";
                $D('move-selector').style.color = "";
            } else {
                doMove = true;
                $D('move-selector').style.backgroundColor = "black";
                $D('move-selector').style.color = "lightgray";
            }
        }

        function detectPad() {
            var c = $D('canvas'), p = c.parentNode;
            c.width = 10;
            c.height = 10;
            padW = c.offsetWidth - 10;
            padH = c.offsetHeight - 10;
            message("padW: " + padW + ", padH: " + padH);
        }

        function doResize() {
            var p = $D('canvas').parentNode;
            message("doResize1: [" + (p.offsetWidth - padW) +
                    "," + (p.offsetHeight - padH) + "]");
            display.viewportChange(0, 0,
                p.offsetWidth - padW, p.offsetHeight - padH);
            /*
            var pos, new_w, new_h;pos
            pos = Util.getPosition($D('canvas').parentNode);
            new_w = window.innerWidth - pos.x;
            new_h = window.innerHeight - pos.y;
            display.viewportChange(0, 0, new_w, new_h);
            */
        }

        window.onload = function() {
            detectPad();

            display = new Display({'target': $D('canvas')});
            display.resize(1600, 1024);
            display.set_viewport(true);
            ctx = display.get_context();

            mouse    = new Mouse({'target': $D('canvas'),
                                'onMouseButton': mouseButton,
                                'onMouseMove': mouseMove});
            mouse.grab();


            Util.addEvent(window, 'resize', doResize);
            // Shrink viewport for first resize call so that the
            // scrollbars are disabled
            display.viewportChange(0, 0, 10, 10);
            setTimeout(doResize, 1);
            setInterval(dirtyRedraw, 50);

            message("Display initialized");
        };
    </script>
</html>