## SwingOSC – Java-based GUI classes

This class is meant as an emulation of SCUserView. last mod: 17-Jul-09 sciss
Also refer to JSCView for different behaviour affecting all widgets

different behaviour
refresh | see the introductory paragraph for SwingOSC's refresh policy
view bounds | here is no way to paint outside the logical bounds of a swing view, all draw operations are automatically clipped to the view's bounds.
extended functionality
focusVisible_ | allows you to switch off the default focus border painting
refreshOnFocus_ | allows you to switch off refreshing as result of focus change

# JSCUserView

Note: please use the abstraction layer UserView if possible!

`JSCUserView` is a user-definable view intended mainly for use with JPen and drawHooks.

See also: JSCWindow, JPen, JSCTabletView.

## drawFunc_

Sets the function which should be evaluated if the view is refreshed. Note: A refresh happens if either of the following conditions meet:

- you call `refresh` on the view.
- you call `refresh` on the window that contains the view.
- the view and all its parents including the window are visible, and either
  - you call `drawFunc_` on the view
  - or the view is resized.
  - or `refreshOnFocus` is `true` and the view gains or looses focus.

Example:

    (
        var func;

        func = { arg me;
            JPen.use {
                // clipping into the boundingbox
                JPen.addRect( me.bounds.moveTo( 0, 0 ));
                JPen.clip;

                // draw background
                // (alternatively use addRect)
                JPen.fillColor = Color.gray( 0.5 );
                JPen.moveTo( 0 @ 0 );
                JPen.lineTo( me.bounds.width @ 0 );
                JPen.lineTo( me.bounds.width @ me.bounds.height );
                JPen.lineTo( 0 @ me.bounds.height );
                JPen.lineTo( 0 @ 0 );
                JPen.fill;

                10.do {
                    JPen.color = Color.red( rrand( 0.0, 1 ), rrand( 0.0, 0.5 ));
                    JPen.addArc( 400.exprand( 2 ) @ 100.rand, rrand( 10, 100 ), 2pi.rand, pi );
                    JPen.perform([ \stroke, \fill ].choose );
                }
            }
        };

        w = JSCWindow.new( "DrawFunc Examples" ).front;
        w.view.background_( Color.white );
        3.do { arg i;
            v = JSCUserView( w, Rect( 20 + (i * 120), 100, 100, 100 ));
            v.drawFunc = func;
        };
    )

    v.refresh;  // updates the last created (third) view
    w.refresh;  // updates all user views in the window

## Focus Behaviour

    // these only work in SwingOSC:
    v.refreshOnFocus = false;   // do not re-evaluate drawFunc when focus is gained or lost (can be more efficient)
    v.focusVisible = false;  // don't paint focus border. you can do yourself in the drawFunc, checking for me.hasFocus!

## relativeOrigin_

Sets the coordinate origin of JPen in the view's `drawFunc` to be relative to the view's top left corner (when `true`, which is the default) or to be relative to the window's top left corner (when `false`).

    (
        w = JSCWindow.new( "relativeOrigin = true" );
        w.view.background_( Color.white );
        3.do { arg i;
            v = JSCUserView(w, Rect( 20 + (i * 120), 100, 100, 100 ))
    // this code not needed since it's the default
    //          .relativeOrigin_( true )
                .drawFunc_({
                    JPen.strokeRect( Rect( 0, 0, 100, 100 ));
                    JPen.line(   0 @ 0, 100 @ 100 );
                    JPen.line( 100 @ 0,   0 @ 100 );
                    JPen.stroke;
                });
        };
        w.front;
    )

You can achieve a similar result with `relativeOrigin` being `false`, by translating the coordinate system at the beginning of the `drawFunc`:

    (
        w = JSCWindow.new( "relativeOrigin = false" );
        w.view.background_( Color.white );
        3.do { arg i;
            v = JSCUserView(w, Rect( 20 + (i * 120), 100, 100, 100 ))
                .relativeOrigin_( false )
                .drawFunc_({ arg view; var bounds;
                    bounds = view.bounds;
                    JPen.translate( bounds.left, bounds.top );
                    JPen.strokeRect( Rect( 0, 0, 100, 100 ));
                    JPen.line(   0 @ 0, 100 @ 100 );
                    JPen.line( 100 @ 0,   0 @ 100 );
                    JPen.stroke;
                })
        };
        w.front;
    )

The effect of `relativeOrigin` setting regarding the reported mouse coordinates is shown in the section mouseUpAction_.

## keyDownAction_

Sets the function which should be evaluated if the view is in focus and a key is pressed. This function will be passed four arguments: the view itself, the key pressed as a Char, the modifier keys Integer (shift, alt, etc.), and the unicode value Integer. See JSCView for more details.

    (
        // select the window, type something and watch the post window
            w = JSCWindow.new( "select this window and type something" );
            c = JSCUserView( w, w.view.bounds );
            c.keyDownAction = { arg view, char, modifiers, unicode, keycode;
                [char, modifiers, unicode, keycode].postln;
                c.drawFunc = {
                    JPen.font       = JFont( "Helvetica", 70 );
                    JPen.fillColor  = Color.blue( 0.3, 0.5 );
                    JPen.stringAtPoint( char.asString, 180 @ 150 );
                };
            };
            w.front; c.focus;
    )

## mouseDownAction_

Sets the function which should be evaluated if the mouse is at the beginning of tracking (mouse-down). This function will be passed four arguments: theView, x coordinate, y coordinate, and keyboard modifiers.

## mouseMoveAction_

Sets the function which should be evaluated if the mouse is tracked. This function will be passed four arguments: theView, x coordinate, y coordinate, and keyboard modifiers.

## mouseUpAction_

Sets the function which should be evaluated if the mouse is at the end of tracking (mouse-up). This function will be passed four arguments: theView, x coordinate, y coordinate, and keyboard modifiers.

Example:

    (
        var drawFunc, beginTrackFunc, endTrackFunc, trackFunc, sat = 0, absX;

        drawFunc = { arg me;
            JPen.use {
                // clipping into the boundingbox
                JPen.addRect( me.bounds.moveTo( 0, 0 ));
                JPen.clip;

                // draw background
                JPen.fillColor = Color.gray( sat );
                JPen.addRect( me.bounds.moveTo( 0, 0 ));
                JPen.fill;

                10.do {
                    JPen.color = Color.red( rrand( 0.0, 1 ), rrand( 0.0, 0.5 ));
                    JPen.addArc( 400.exprand( 2 ) @ 100.rand, rrand( 10, 100 ), 2pi.rand, pi );
                    JPen.perform([ \stroke, \fill ].choose );
                }
            }
        };
        beginTrackFunc = { arg me, x, y, mod;
            absX = x;
            postf( "begin path: x=%\n", absX );
        };
        endTrackFunc = { arg me, x, y, mod;
            postf( "end path: (absX-x)=%\n", (absX - x));
        };
        trackFunc = { arg me, x, y, mod;
            sat = (absX - x) / 100;
            me.refresh;
        };

        w = JSCWindow.new.front;
        w.view.background_(Color.white);
        3.do { arg i;
            v = JSCUserView( w, Rect( 20 + (i * 120), 100, 100, 100 ));
            v.drawFunc = drawFunc;
            v.mouseDownAction = beginTrackFunc;
            v.mouseUpAction = endTrackFunc;
            v.mouseMoveAction = trackFunc;
        };
    )

Important: the mouse coordinates reported depend on the setting of `relativeOrigin`: for the default of `true`, they are relative to the left-top corner of the view, for `false`, they are relative to the view's parent container top-left. That means that the mouse coordinates always refer to the same origin as the pen drawing coordinates. As a result, you can always plug the mouse coordinates directly into drawing commands without the need of translation, regardless of the `relativeOrigin` setting:

    (
        w = JSCWindow.new;

        2.do({ arg i; var v, f;
            v = JSCUserView( w, Rect( 20 + (i * 180), 30, 150, 300 ))
                .relativeOrigin_( i == 0 )
                .background_( Color.rand )
                .focusVisible_( false );
            f = { arg view, x, y; v.drawFunc = { arg view; JPen.stringAtPoint( "%, %".format( x, y ), (x + 12) @ (y - 4 ))}};
            v.mouseDownAction = f;
            v.mouseMoveAction = f;
            x = v;
        });
        w.front;
    )

A final example for mouse control shows how it can be used to draw live on the view:

    // NOTE: in swing, do not rely on correct painting
    // when components overlap. components should never overlap.
    // therefore, in this example, we use the drawFunc of the user view
    // and not the window to paint (see SCUserView help for comparison)

    // NOTE: after some drawing, the number of OSC packets sent
    // during each refresh increases which results in more sluggish
    // updates

    (
        var w, txt, tmppoints, all;
        tmppoints = [];

        w = JSCWindow( "draw on me", Rect( 128, 64, 340, 360 ));
        v = JSCUserView( w, Rect( 0, 0, 340, 360 ))
            .canFocus_( false )
            .mouseMoveAction_({ arg view,x,y;
                tmppoints = tmppoints.add( x @ y );
                view.refresh;
            })
            .mouseUpAction_({ arg view, x, y;
                all = all.add( tmppoints.copy );
                tmppoints = [];
                view.refresh;
            })
            .drawFunc_({
                JPen.use {
                    JPen.width = 1;
                    JPen.beginPath;

                    tmppoints.do { arg p, i;
                        if( i == 0, {
                            JPen.moveTo( p );
                        }, {
                            JPen.lineTo( p );
                        });
                    };
                    all.do{ arg points;
                        points.do { arg p, i;
                            if( i == 0, {
                                JPen.moveTo( p );
                            }, {
                                JPen.lineTo( p );
                            });
                        };
                    };
                    JPen.stroke;
                };
            });

        w.front;
    )

## clearOnRefresh_ / clearDrawing

`clearOnRefresh_` sets the behaviour for refreshing the view. If this flag is `true` (the default) the view will be cleared before each refresh call, otherwise it will draw on top of the previous graphics.

Example:

    (
        var theta = 0;
        w = JSCWindow( "clearOnRefresh = false", resizable: false );
        x = JSCUserView( w, w.view.bounds )
            .focusVisible_( false )
            .clearOnRefresh_( false )
            .drawFunc_({ arg view;
                var x = 20 * sin( theta ), y = 42 * cos( theta ), b = view.bounds;
                theta = theta + 0.01;
                JPen.fillColor = Color.red( 0.2, 0.05 );
                JPen.fillRect( Rect( 0, 0, b.width, b.height ));
                JPen.strokeColor = Color.white;
                JPen.translate( b.width * 0.5, b.height * 0.5 );
                6.do({ arg i;
                    JPen.rotate( theta * (1 - (i / 6)) );
                    JPen.scale( 0.7 + (i * 0.4), 0.4 + (i * 0.5) );
                    JPen.strokeOval( Rect.aboutPoint( x @ y, 60, 40 ));
                });
            });

        w.front;
        fork { while({ x.isClosed.not }, { x.refresh; (1/5).wait })};
    )

You can explicitly trigger a request to clear the drawing at the next view's refresh, by calling the `clearDrawing` method:

    (
        w.name = "Press mouse to clear drawing";
        x.mouseDownAction_({ arg view, x, y; view.clearDrawing });
    )

## refreshOnFocus_ (SwingOSC only!)

To "freeze" a drawing, that is, to prevent it to be re-drawn, use either `.canFocus_( false )` or `.refreshOnFocus_( false )`:

    (
    var func, views;

    func = {
        10.do {
            JPen.color = Color.red( rrand(0.0, 1), rrand( 0.0, 0.5 ));
            JPen.addArc( 400.exprand( 2 ) @ 100.rand, rrand( 10, 100 ), 2pi.rand, pi );
            JPen.perform([ \stroke, \fill ].choose );
        };
    };

    w = JSCWindow.new( "Frozen drawing" );
    w.view.background_( Color.white );
    views = { arg i;
        v = JSCUserView( w, Rect( 20 + (i * 120), 100, 100, 100 ))
            .refreshOnFocus_( false )
            .drawFunc_( func );
    } ! 3;
    w.front;
    )