From: Kevin Lamonte Date: Sat, 23 Feb 2019 16:46:43 +0000 (-0600) Subject: tiling window manager example X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=528874e2e78eea5cdfd000baa208ac1c2f2ce4a0;p=nikiroo-utils.git tiling window manager example --- diff --git a/.gitignore b/.gitignore index 733399a..7c3b76b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ misc/** pmd.bash pmd-results.html +examples/*.sh diff --git a/README.md b/README.md index 2a62f1c..39ae2a1 100644 --- a/README.md +++ b/README.md @@ -137,9 +137,19 @@ it and you'll see an application like this: ![The Example Code Above](/screenshots/readme_application.png?raw=true "The application in the text of README.md") -See the files in jexer.demos for many more detailed examples showing -all of the existing UI controls. The available demos can be run as -follows: + + +More Examples +------------- + +The examples/ folder currently contains: + + * A [prototype tiling window + manager](/examples/JavaTilingWindowManager.java) in less than 250 + lines of code. + +jexer.demos contains official demos showing all of the existing UI +controls. The demos can be run as follows: * 'java -jar jexer.jar' . This will use System.in/out with xterm-like sequences on non-Windows non-Mac platforms. On Windows diff --git a/examples/JexerTilingWindowManager.java b/examples/JexerTilingWindowManager.java new file mode 100644 index 0000000..5b5740a --- /dev/null +++ b/examples/JexerTilingWindowManager.java @@ -0,0 +1,223 @@ +import jexer.TApplication; +import jexer.TTerminalWindow; +import jexer.TWindow; +import jexer.event.TKeypressEvent; +import jexer.event.TMenuEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.menu.TMenu; + +/** + * Implements a simple tiling window manager. A root non-moveable + * non-resizable terminal window is created first, which can be split + * horizontally or vertically. Each new window retains a reference to its + * "parent", and upon closing resizes that parent back to its original size. + * + * This example shows what can be done with minimal changes to stock Jexer + * widgets. You will quickly see that closing a "parent" tile does not cause + * the "child" tile to resize. You could make a real subclass of + * TTerminalWindow that has extra fields and/or communicates more with + * JexerTilingWindowManager to get full coverage of tile creation, + * destruction, placement, movement, and so on. + */ +public class JexerTilingWindowManager extends TApplication { + + /** + * Menu item: split the terminal vertically. + */ + private static final int MENU_SPLIT_VERTICAL = 2000; + + /** + * Menu item: split the terminal horizontally. + */ + private static final int MENU_SPLIT_HORIZONTAL = 2001; + + /** + * Main entry point. + */ + public static void main(String [] args) throws Exception { + // For this application, we must use ptypipe so that the tile shells + // can be aware of their size. + System.setProperty("jexer.TTerminal.ptypipe", "true"); + + JexerTilingWindowManager jtwm = new JexerTilingWindowManager(); + (new Thread(jtwm)).start(); + } + + /** + * Public constructor chooses the ECMA-48 / Xterm backend. + */ + public JexerTilingWindowManager() throws Exception { + super(BackendType.XTERM); + + // The stock tool menu has items for redrawing the screen, opening + // images, and (when using the Swing backend) setting the font. + addToolMenu(); + + // We will have one menu containing a mix of new and stock commands + TMenu tileMenu = addMenu("&Tile"); + + // New commands for this example: split vertical and horizontal. + tileMenu.addItem(MENU_SPLIT_VERTICAL, "&Vertical Split"); + tileMenu.addItem(MENU_SPLIT_HORIZONTAL, "&Horizontal Split"); + + // Stock commands: a new shell with resizable window, previous, next, + // close, and exit program. + tileMenu.addItem(TMenu.MID_SHELL, "&Floating"); + tileMenu.addSeparator(); + tileMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS); + tileMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT); + tileMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE); + tileMenu.addSeparator(); + tileMenu.addDefaultItem(TMenu.MID_EXIT); + + // Spin up the root tile + TTerminalWindow rootTile = makeTile(0, 0, getScreen().getWidth(), + getDesktopBottom() - 1, null); + + // Let's add some bling! Enable focus-follows-mouse. + setFocusFollowsMouse(true); + } + + /** + * Process menu events. + */ + @Override + protected boolean onMenu(TMenuEvent event) { + if (event.getId() == MENU_SPLIT_VERTICAL) { + splitVertical(); + return true; + } + if (event.getId() == MENU_SPLIT_HORIZONTAL) { + splitHorizontal(); + return true; + } + + return super.onMenu(event); + } + + /** + * Perform the vertical split. + */ + private void splitVertical() { + TWindow window = getActiveWindow(); + if (!(window instanceof TTerminalWindow)) { + return; + } + + TTerminalWindow tile = (TTerminalWindow) window; + // Give the extra column to the new tile. + int newWidth = (tile.getWidth() + 1) / 2; + int newY = tile.getY() - 1; + int newX = tile.getX() + tile.getWidth() - newWidth; + makeTile(newX, newY, newWidth, tile.getHeight(), tile); + tile.setWidth(tile.getWidth() - newWidth); + tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + tile.getWidth(), tile.getHeight())); + } + + /** + * Perform the horizontal split. + */ + private void splitHorizontal() { + TWindow window = getActiveWindow(); + if (!(window instanceof TTerminalWindow)) { + return; + } + + TTerminalWindow tile = (TTerminalWindow) window; + // Give the extra row to the new tile. + int newHeight = (tile.getHeight() + 1) / 2; + int newY = tile.getY() - 1 + tile.getHeight() - newHeight; + int newX = tile.getX(); + makeTile(newX, newY, tile.getWidth(), newHeight, tile); + tile.setHeight(tile.getHeight() - newHeight); + tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + tile.getWidth(), tile.getHeight())); + } + + /** + * Create a non-resizable non-movable terminal window. + * + * @param x the column number to place the top-left corner at. 0 is the + * left-most column. + * @param y the row number to place the top-left corner at. 0 is the + * top-most column. + * @param width the width of the window + * @param height the height of the window + * @param otherTile the other tile to resize when this window closes + */ + private TTerminalWindow makeTile(int x, int y, int width, int height, + final TTerminalWindow otherTile) { + + // We pass flags to disable the zoom (maximize) button, disable + // "smart" window placement, and set the specific location. + TTerminalWindow tile = new TTerminalWindow(this, x, y, + TWindow.NOZOOMBOX | TWindow.ABSOLUTEXY, + new String[] { "/bin/bash", "--login" }, true) { + + /** + * When this terminal closes, if otherTile is defined then resize + * it to overcover me. + */ + @Override + public void onClose() { + super.onClose(); + + if (otherTile != null) { + if (otherTile.getX() != getX()) { + // Undo the vertical split + otherTile.setX(Math.min(otherTile.getX(), getX())); + otherTile.setWidth(otherTile.getWidth() + getWidth()); + } + if (otherTile.getY() != getY()) { + otherTile.setY(Math.min(otherTile.getY(), getY())); + otherTile.setHeight(otherTile.getHeight() + getHeight()); + } + otherTile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + otherTile.getWidth(), otherTile.getHeight())); + } + } + + /** + * Prevent the user from resizing or moving this window. + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + super.onMouseDown(mouse); + stopMovements(); + } + + /** + * Prevent the user from resizing or moving this window. + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + super.onKeypress(keypress); + stopMovements(); + } + + /** + * Permit the user to use all of the menu items. + */ + @Override + public void onIdle() { + super.onIdle(); + removeShortcutKeypress(jexer.TKeypress.kbAltT); + removeShortcutKeypress(jexer.TKeypress.kbF6); + } + + }; + + // The initial window size was stock VT100 80x24. Change that now, + // and then call onResize() to notify ptypipe to set the shell's + // window size. + tile.setWidth(width); + tile.setHeight(height); + tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + tile.getWidth(), tile.getHeight())); + + return tile; + } + +}