From: Kevin Lamonte Date: Sun, 19 Mar 2017 01:02:09 +0000 (-0400) Subject: smart window placement X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=commitdiff_plain;h=a7986f7b289a17ca812a2f0cf04e48071accd636 smart window placement --- diff --git a/docs/TODO.md b/docs/TODO.md index f3e1233..4bc66c7 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -5,11 +5,6 @@ Jexer TODO List Roadmap ------- -0.0.4 - -- TWindow - - "Smart placement" for new windows - 0.0.5 - TEditor diff --git a/docs/worklog.md b/docs/worklog.md index 89cbef5..bed1e2d 100644 --- a/docs/worklog.md +++ b/docs/worklog.md @@ -1,6 +1,17 @@ Jexer Work Log ============== +March 18, 2017 + +TStatusBar is working, as is "smart" window placement. Overall this +is looking quite nice. Found a lot of other small paper cut items and +fixed them. It looks absolutely gorgeous on Mac now. + +Tomorrow I will get to the public wifi and get this uploaded. + +Time to call this 0.0.4 now though. We are up to 32,123 lines of +code. + March 17, 2017 Jexer is coming back to active development status. I had a lot of diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index ff9c19a..9f15ccb 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -79,6 +79,12 @@ public class TApplication implements Runnable { */ private static final boolean debugEvents = false; + /** + * If true, do "smart placement" on new windows that are not specified to + * be centered. + */ + private static final boolean smartWindowPlacement = true; + /** * Two backend types are available. */ @@ -1285,12 +1291,19 @@ public class TApplication implements Runnable { * @param window new window to add */ public final void addWindow(final TWindow window) { + + // Do not add menu windows to the window list. + if (window instanceof TMenu) { + return; + } + synchronized (windows) { // Do not allow a modal window to spawn a non-modal window. If a // modal window is active, then this window will become modal // too. if (modalWindowActive()) { window.flags |= TWindow.MODAL; + window.flags |= TWindow.CENTERED; } for (TWindow w: windows) { if (w.isActive()) { @@ -1303,6 +1316,12 @@ public class TApplication implements Runnable { window.setZ(0); window.setActive(true); window.onFocus(); + + if (((window.flags & TWindow.CENTERED) == 0) + && smartWindowPlacement) { + + doSmartPlacement(window); + } } } @@ -1424,6 +1443,131 @@ public class TApplication implements Runnable { } } + /** + * Place a window to minimize its overlap with other windows. + * + * @param window the window to place + */ + public final void doSmartPlacement(final TWindow window) { + // This is a pretty dumb algorithm, but seems to work. The hardest + // part is computing these "overlap" values seeking a minimum average + // overlap. + int xMin = 0; + int yMin = desktopTop; + int xMax = getScreen().getWidth() - window.getWidth() + 1; + int yMax = desktopBottom - window.getHeight() + 1; + if (xMax < xMin) { + xMax = xMin; + } + if (yMax < yMin) { + yMax = yMin; + } + + if ((xMin == xMax) && (yMin == yMax)) { + // No work to do, bail out. + return; + } + + // Compute the overlap matrix without the new window. + int width = getScreen().getWidth(); + int height = getScreen().getHeight(); + int overlapMatrix[][] = new int[width][height]; + for (TWindow w: windows) { + if (window == w) { + continue; + } + for (int x = w.getX(); x < w.getX() + w.getWidth(); x++) { + if (x == width) { + continue; + } + for (int y = w.getY(); y < w.getY() + w.getHeight(); y++) { + if (y == height) { + continue; + } + overlapMatrix[x][y]++; + } + } + } + + long oldOverlapTotal = 0; + long oldOverlapN = 0; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + oldOverlapTotal += overlapMatrix[x][y]; + if (overlapMatrix[x][y] > 0) { + oldOverlapN++; + } + } + } + + + double oldOverlapAvg = (double) oldOverlapTotal / (double) oldOverlapN; + boolean first = true; + int windowX = window.getX(); + int windowY = window.getY(); + + // For each possible (x, y) position for the new window, compute a + // new overlap matrix. + for (int x = xMin; x < xMax; x++) { + for (int y = yMin; y < yMax; y++) { + + // Start with the matrix minus this window. + int newMatrix[][] = new int[width][height]; + for (int mx = 0; mx < width; mx++) { + for (int my = 0; my < height; my++) { + newMatrix[mx][my] = overlapMatrix[mx][my]; + } + } + + // Add this window's values to the new overlap matrix. + long newOverlapTotal = 0; + long newOverlapN = 0; + // Start by adding each new cell. + for (int wx = x; wx < x + window.getWidth(); wx++) { + if (wx == width) { + continue; + } + for (int wy = y; wy < y + window.getHeight(); wy++) { + if (wy == height) { + continue; + } + newMatrix[wx][wy]++; + } + } + // Now figure out the new value for total coverage. + for (int mx = 0; mx < width; mx++) { + for (int my = 0; my < height; my++) { + newOverlapTotal += newMatrix[x][y]; + if (newMatrix[mx][my] > 0) { + newOverlapN++; + } + } + } + double newOverlapAvg = (double) newOverlapTotal / (double) newOverlapN; + + if (first) { + // First time: just record what we got. + oldOverlapAvg = newOverlapAvg; + first = false; + } else { + // All other times: pick a new best (x, y) and save the + // overlap value. + if (newOverlapAvg < oldOverlapAvg) { + windowX = x; + windowY = y; + oldOverlapAvg = newOverlapAvg; + } + } + + } // for (int x = xMin; x < xMax; x++) + + } // for (int y = yMin; y < yMax; y++) + + // Finally, set the window's new coordinates. + window.setX(windowX); + window.setY(windowY); + } + // ------------------------------------------------------------------------ // TMenu management ------------------------------------------------------- // ------------------------------------------------------------------------ diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index a0a9786..6d89647 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -113,9 +113,6 @@ public final class TMenu extends TWindow { super(parent, label, x, y, parent.getScreen().getWidth(), parent.getScreen().getHeight()); - // My parent constructor added me as a window, get rid of that - parent.closeWindow(this); - // Setup the menu shortcut mnemonic = new MnemonicString(label); setTitle(mnemonic.getRawLabel());