From a06459bd6b0e65c9b590dbdf6ed9349043119215 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Fri, 13 Mar 2015 06:50:35 -0400 Subject: [PATCH] draggable window --- demos/Demo1.java | 158 +++++++++++++++++++++++++--- src/jexer/TApplication.java | 203 +++++++++++++++++++++++++++--------- src/jexer/TWidget.java | 9 ++ src/jexer/TWindow.java | 25 ++++- 4 files changed, 330 insertions(+), 65 deletions(-) diff --git a/demos/Demo1.java b/demos/Demo1.java index 62fce24..29d0345 100644 --- a/demos/Demo1.java +++ b/demos/Demo1.java @@ -31,9 +31,150 @@ * 02110-1301 USA */ -import jexer.bits.*; -import jexer.TApplication; -import jexer.session.TTYSessionInfo; +import jexer.*; + +class DemoMainWindow extends TWindow { + /* + // Timer that increments a number + private TTimer timer; + + // The modal window is a more low-level example of controlling a window + // "from the outside". Most windows will probably subclass TWindow and + // do this kind of logic on their own. + private TWindow modalWindow; + private void openModalWindow() { + modalWindow = application.addWindow("Demo Modal Window", 0, 0, + 58, 15, TWindow.Flag.MODAL); + modalWindow.addLabel("This is an example of a very braindead modal window.", 1, 1); + modalWindow.addLabel("Modal windows are centered by default.", 1, 2); + modalWindow.addButton("&Close", (modalWindow.width - 8)/2, + modalWindow.height - 4, &modalWindowClose); + } + private void modalWindowClose() { + application.closeWindow(modalWindow); + } + + /// This is an example of having a button call a function. + private void openCheckboxWindow() { + new DemoCheckboxWindow(application); + } + + /// We need to override onClose so that the timer will no longer be + /// called after we close the window. TTimers currently are completely + /// unaware of the rest of the UI classes. + override public void onClose() { + application.removeTimer(timer); + } + */ + + /// Constructor + public DemoMainWindow(TApplication parent) { + this(parent, CENTERED | RESIZABLE); + } + + /// Constructor + public DemoMainWindow(TApplication parent, int flags) { + // Construct a demo window. X and Y don't matter because it will be + // centered on screen. + super(parent, "Demo Window", 0, 0, 60, 23, flags); + + int row = 1; + + /* + // Add some widgets + if (!isModal) { + addLabel("Message Boxes", 1, row); + addButton("&MessageBoxes", 35, row, + { + new DemoMsgBoxWindow(application); + } + ); + } + row += 2; + + addLabel("Open me as modal", 1, row); + addButton("W&indow", 35, row, + { + new DemoMainWindow(application, Flag.MODAL); + } + ); + + row += 2; + + addLabel("Variable-width text field:", 1, row); + addField(35, row++, 15, false, "Field text"); + + addLabel("Fixed-width text field:", 1, row); + addField(35, row, 15, true); + row += 2; + + if (!isModal) { + addLabel("Radio buttons and checkboxes", 1, row); + addButton("&Checkboxes", 35, row, &openCheckboxWindow); + } + row += 2; + + if (!isModal) { + addLabel("Editor window", 1, row); + addButton("Edito&r", 35, row, + { + new TEditor(application, 0, 0, 60, 15); + } + ); + } + row += 2; + + if (!isModal) { + addLabel("Text areas", 1, row); + addButton("&Text", 35, row, + { + new DemoTextWindow(application); + } + ); + } + row += 2; + + if (!isModal) { + addLabel("Tree views", 1, row); + addButton("Tree&View", 35, row, + { + new DemoTreeViewWindow(application); + } + ); + } + row += 2; + + version(Posix) { + if (!isModal) { + addLabel("Terminal", 1, row); + addButton("Termi&nal", 35, row, + { + application.openTerminal(0, 0); + } + ); + } + row += 2; + } + + TProgressBar bar = addProgressBar(1, row, 22); + row++; + TLabel timerLabel = addLabel("Timer", 1, row); + timer = parent.addTimer(100, + { + static int i = 0; + auto writer = appender!dstring(); + formattedWrite(writer, "Timer: %d", i); + timerLabel.text = writer.data; + timerLabel.width = cast(uint)timerLabel.text.length; + if (i < 100) { + i++; + } + bar.value = i; + parent.repaint = true; + }, true); + */ + } +} /** * The demo application itself. @@ -44,16 +185,7 @@ class DemoApplication extends TApplication { */ public DemoApplication() throws Exception { super(null, null); - /* - try { - ColorTheme theme = new ColorTheme(); - TTYSessionInfo tty = new TTYSessionInfo(); - System.out.println("width: " + tty.getWindowWidth()); - System.out.println("height: " + tty.getWindowHeight()); - } catch (Exception e) { - e.printStackTrace(); - } - */ + new DemoMainWindow(this); } } diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index d4a6610..ef9bd04 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -33,6 +33,7 @@ package jexer; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -98,6 +99,11 @@ public class TApplication { return theme; } + /** + * The top-level windows (but not menus). + */ + List windows; + /** * When true, exit the application. */ @@ -168,28 +174,24 @@ public class TApplication { backend = new ECMA48Backend(input, output); theme = new ColorTheme(); - desktopBottom = backend.getScreen().getHeight() - 1; + desktopBottom = getScreen().getHeight() - 1; eventQueue = new LinkedList(); + windows = new LinkedList(); } /** * Invert the cell at the mouse pointer position. */ private void drawMouse() { - CellAttributes attr = backend.getScreen().getAttrXY(mouseX, mouseY); + CellAttributes attr = getScreen().getAttrXY(mouseX, mouseY); attr.setForeColor(attr.getForeColor().invert()); attr.setBackColor(attr.getBackColor().invert()); - backend.getScreen().putAttrXY(mouseX, mouseY, attr, false); + getScreen().putAttrXY(mouseX, mouseY, attr, false); flush = true; - /* - if (windows.length == 0) { + if (windows.size() == 0) { repaint = true; } - */ - // TODO: remove this repaint after the above if (windows.length == 0) - // can be used again. - repaint = true; } /** @@ -210,31 +212,32 @@ public class TApplication { boolean cursor = false; // Start with a clean screen - backend.getScreen().clear(); + getScreen().clear(); // Draw the background CellAttributes background = theme.getColor("tapplication.background"); - backend.getScreen().putAll(GraphicsChars.HATCH, background); + getScreen().putAll(GraphicsChars.HATCH, background); - /* // Draw each window in reverse Z order - TWindow [] sorted = windows.dup; - sorted.sort.reverse; - foreach (w; sorted) { - w.drawChildren(); + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow window: sorted) { + window.drawChildren(); } + /* // Draw the blank menubar line - reset the screen clipping first so // it won't trim it out. - backend.getScreen().resetClipping(); - backend.getScreen().hLineXY(0, 0, backend.getScreen().getWidth(), ' ', + getScreen().resetClipping(); + getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ', theme.getColor("tmenu")); // Now draw the menus. int x = 1; - foreach (m; menus) { + for (TMenu m: menus) { CellAttributes menuColor; CellAttributes menuMnemonicColor; - if (m.active) { + if (menu.active) { menuColor = theme.getColor("tmenu.highlighted"); menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); } else { @@ -242,38 +245,37 @@ public class TApplication { menuMnemonicColor = theme.getColor("tmenu.mnemonic"); } // Draw the menu title - backend.getScreen().hLineXY(x, 0, cast(int)m.title.length + 2, ' ', + getScreen().hLineXY(x, 0, menu.title.length() + 2, ' ', menuColor); - backend.getScreen().putStrXY(x + 1, 0, m.title, menuColor); + getScreen().putStrXY(x + 1, 0, menu.title, menuColor); // Draw the highlight character - backend.getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, + getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, m.mnemonic.shortcut, menuMnemonicColor); - if (m.active) { - m.drawChildren(); + if (menu.active) { + menu.drawChildren(); // Reset the screen clipping so we can draw the next title. - backend.getScreen().resetClipping(); + getScreen().resetClipping(); } - x += m.title.length + 2; + x += menu.title.length + 2; } - foreach (m; subMenus) { + for (TMenu menu: subMenus) { // Reset the screen clipping so we can draw the next sub-menu. - backend.getScreen().resetClipping(); - m.drawChildren(); + getScreen().resetClipping(); + menu.drawChildren(); } - */ + */ // Draw the mouse pointer drawMouse(); - /* // Place the cursor if it is visible TWidget activeWidget = null; - if (sorted.length > 0) { - activeWidget = sorted[$ - 1].getActiveChild(); - if (activeWidget.hasCursor) { - backend.getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), + if (sorted.size() > 0) { + activeWidget = sorted.get(sorted.size() - 1).getActiveChild(); + if (activeWidget.visibleCursor()) { + getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), activeWidget.getCursorAbsoluteY()); cursor = true; } @@ -281,9 +283,8 @@ public class TApplication { // Kill the cursor if (cursor == false) { - backend.getScreen().hideCursor(); + getScreen().hideCursor(); } - */ // Flush the screen contents backend.flushScreen(); @@ -388,9 +389,9 @@ public class TApplication { // Screen resize if (event instanceof TResizeEvent) { TResizeEvent resize = (TResizeEvent) event; - backend.getScreen().setDimensions(resize.getWidth(), + getScreen().setDimensions(resize.getWidth(), resize.getHeight()); - desktopBottom = backend.getScreen().getHeight() - 1; + desktopBottom = getScreen().getHeight() - 1; repaint = true; mouseX = 0; mouseY = 0; @@ -407,6 +408,9 @@ public class TApplication { } } + // TODO: change to two separate threads + handleEvent(event); + /* // Put into the main queue @@ -431,11 +435,115 @@ public class TApplication { } + /** + * Dispatch one event to the appropriate widget or application-level + * event handler. + * + * @param event the input event to consume + */ + private final void handleEvent(TInputEvent event) { + + /* + // std.stdio.stderr.writefln("Handle event: %s", event); + + // Special application-wide events ----------------------------------- + + // Peek at the mouse position + if (auto mouse = cast(TMouseEvent)event) { + // See if we need to switch focus to another window or the menu + checkSwitchFocus(mouse); + } + + // Handle menu events + if ((activeMenu !is null) && (!cast(TCommandEvent)event)) { + TMenu menu = activeMenu; + if (auto mouse = cast(TMouseEvent)event) { + + while (subMenus.length > 0) { + TMenu subMenu = subMenus[$ - 1]; + if (subMenu.mouseWouldHit(mouse)) { + break; + } + if ((mouse.type == TMouseEvent.Type.MOUSE_MOTION) && + (!mouse.mouse1) && + (!mouse.mouse2) && + (!mouse.mouse3) && + (!mouse.mouseWheelUp) && + (!mouse.mouseWheelDown) + ) { + break; + } + // We navigated away from a sub-menu, so close it + closeSubMenu(); + } + + // Convert the mouse relative x/y to menu coordinates + assert(mouse.x == mouse.absoluteX); + assert(mouse.y == mouse.absoluteY); + if (subMenus.length > 0) { + menu = subMenus[$ - 1]; + } + mouse.x -= menu.x; + mouse.y -= menu.y; + } + menu.handleEvent(event); + return; + } + + if (auto keypress = cast(TKeypressEvent)event) { + // See if this key matches an accelerator, and if so dispatch the + // menu event. + TKeypress keypressLowercase = toLower(keypress.key); + TMenuItem *item = (keypressLowercase in accelerators); + if (item !is null) { + // Let the menu item dispatch + item.dispatch(); + return; + } else { + // Handle the keypress + if (onKeypress(keypress)) { + return; + } + } + } + + if (auto cmd = cast(TCommandEvent)event) { + if (onCommand(cmd)) { + return; + } + } + + if (auto menu = cast(TMenuEvent)event) { + if (onMenu(menu)) { + return; + } + } + */ + + // Dispatch events to the active window ------------------------------- + for (TWindow window: windows) { + if (window.active) { + if (event instanceof TMouseEvent) { + TMouseEvent mouse = (TMouseEvent) event; + // Convert the mouse relative x/y to window coordinates + assert (mouse.getX() == mouse.getAbsoluteX()); + assert (mouse.getY() == mouse.getAbsoluteY()); + mouse.setX(mouse.getX() - window.x); + mouse.setY(mouse.getY() - window.y); + } + // System.err("TApplication dispatch event: %s\n", event); + window.handleEvent(event); + break; + } + } + } + /** * Do stuff when there is no user input. */ private void doIdle() { /* + TODO // Now run any timers that have timed out auto now = Clock.currTime; TTimer [] keepTimers; @@ -600,20 +708,17 @@ public class TApplication { * @param window new window to add */ public final void addWindow(final TWindow window) { - /* - TODO // Do not allow a modal window to spawn a non-modal window - if ((windows.length > 0) && (windows[0].isModal())) { - assert(window.isModal()); + if ((windows.size() > 0) && (windows.get(0).isModal())) { + assert (window.isModal()); } - foreach (w; windows) { + for (TWindow w: windows) { w.active = false; - w.z++; + w.setZ(w.getZ() + 1); } - windows ~= window; + windows.add(window); window.active = true; - window.z = 0; - */ + window.setZ(0); } diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 6312d08..4d9e22c 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -153,6 +153,15 @@ public abstract class TWidget { */ private boolean hasCursor = false; + /** + * See if this widget has a visible cursor. + * + * @return if true, this widget has a visible cursor + */ + public final boolean visibleCursor() { + return hasCursor; + } + /** * Cursor column position in relative coordinates. */ diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index b850789..0e94808 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -45,7 +45,7 @@ import static jexer.TKeypress.*; /** * TWindow is the top-level container and drawing surface for other widgets. */ -public class TWindow extends TWidget { +public class TWindow extends TWidget implements Comparable { /** * Window's parent TApplication. @@ -100,6 +100,24 @@ public class TWindow extends TWidget { */ private int z = 0; + /** + * Get Z order. Lower number means more in-front. + * + * @return Z value. Lower number means more in-front. + */ + public final int getZ() { + return z; + } + + /** + * Set Z order. Lower number means more in-front. + * + * @param z the new Z value. Lower number means more in-front. + */ + public final void setZ(final int z) { + this.z = z; + } + /** * If true, then the user clicked on the title bar and is moving the * window. @@ -275,8 +293,9 @@ public class TWindow extends TWidget { * @param that another TWindow instance * @return difference between this.z and that.z */ - public final int compare(final TWindow that) { - return (z - that.z); + @Override + public final int compareTo(final TWindow that) { + return (this.z - that.z); } /** -- 2.27.0