DVUI

placeOnScreen

Adjust start rect based on screen and spawner (like a context menu).

When adding a floating widget or window, often we want to guarantee that it is visible. Additionally, if start is logically connected to a spawning rect (like a context menu spawning a submenu), then jump to the opposite side if needed.

Parameters

#
screen:Rect.Natural
Rect.Natural
spawner:Rect.Natural
Rect.Natural
avoid:PlaceOnScreenAvoid
PlaceOnScreenAvoid
start:Rect.Natural
Rect.Natural

Source

Implementation

#
pub fn placeOnScreen(screen: Rect.Natural, spawner: Rect.Natural, avoid: PlaceOnScreenAvoid, start: Rect.Natural) Rect.Natural {
    var r = start;

    // first move to avoid spawner
    if (!r.intersect(spawner).empty()) {
        switch (avoid) {
            .none => {},
            .horizontal => r.x = spawner.x + spawner.w,
            .vertical => r.y = spawner.y + spawner.h,
        }
    }

    // fix up if we ran off right side of screen
    switch (avoid) {
        .none, .vertical => {
            // if off right, move
            if ((r.x + r.w) > (screen.x + screen.w)) {
                r.x = (screen.x + screen.w) - r.w;
            }

            // if off left, move
            if (r.x < screen.x) {
                r.x = screen.x;
            }

            // if off right, shrink to fit (but not to zero)
            // - if we went to zero, then a window could get into a state where you can
            // no longer see it or interact with it (like if you resize the OS window
            // to zero size and back)
            if ((r.x + r.w) > (screen.x + screen.w)) {
                r.w = @max(24, (screen.x + screen.w) - r.x);
            }
        },
        .horizontal => {
            // if off right, is there more room on left
            if ((r.x + r.w) > (screen.x + screen.w)) {
                if ((spawner.x - screen.x) > (screen.x + screen.w - (spawner.x + spawner.w))) {
                    // more room on left, switch
                    r.x = spawner.x - r.w;

                    if (r.x < screen.x) {
                        // off left, shrink
                        r.x = screen.x;
                        r.w = spawner.x - screen.x;
                    }
                } else {
                    // more room on left, shrink
                    r.w = @max(24, (screen.x + screen.w) - r.x);
                }
            }
        },
    }

    // fix up if we ran off bottom of screen
    switch (avoid) {
        .none, .horizontal => {
            // if off bottom, first try moving
            if ((r.y + r.h) > (screen.y + screen.h)) {
                r.y = (screen.y + screen.h) - r.h;
            }

            // if off top, move
            if (r.y < screen.y) {
                r.y = screen.y;
            }

            // if still off bottom, shrink to fit (but not to zero)
            if ((r.y + r.h) > (screen.y + screen.h)) {
                r.h = @max(24, (screen.y + screen.h) - r.y);
            }
        },
        .vertical => {
            // if off bottom, is there more room on top?
            if ((r.y + r.h) > (screen.y + screen.h)) {
                if ((spawner.y - screen.y) > (screen.y + screen.h - (spawner.y + spawner.h))) {
                    // more room on top, switch
                    r.y = spawner.y - r.h;

                    if (r.y < screen.y) {
                        // off top, shrink
                        r.y = screen.y;
                        r.h = spawner.y - screen.y;
                    }
                } else {
                    // more room on bottom, shrink
                    r.h = @max(24, (screen.y + screen.h) - r.y);
                }
            }
        },
    }

    return r;
}