Add ChatboxPanelManager to allow for more advanced chatbox inputs

This commit is contained in:
Max Weber
2018-10-08 01:36:29 -06:00
parent 33dec14a38
commit 2434557e1a
24 changed files with 1831 additions and 25 deletions

View File

@@ -53,6 +53,7 @@ import net.runelite.client.discord.DiscordService;
import net.runelite.client.game.ClanManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.LootManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.menus.MenuManager;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.rs.ClientUpdateCheckMode;
@@ -141,6 +142,9 @@ public class RuneLite
@Inject
private Provider<LootManager> lootManager;
@Inject
private Provider<ChatboxPanelManager> chatboxPanelManager;
@Inject
@Nullable
private Client client;
@@ -278,6 +282,7 @@ public class RuneLite
eventBus.register(chatMessageManager.get());
eventBus.register(commandManager.get());
eventBus.register(lootManager.get());
eventBus.register(chatboxPanelManager.get());
// Add core overlays
WidgetOverlay.createOverlays(client).forEach(overlayManager::add);

View File

@@ -98,7 +98,7 @@ public class ChatboxInputManager
}
this.open = false;
clientThread.invoke(() -> client.runScript(
ScriptID.CLOSE_CHATBOX_INPUT,
ScriptID.RESET_CHATBOX_INPUT,
1,
1
));

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game.chatbox;
/**
* A modal input that lives in the chatbox panel.
*/
public abstract class ChatboxInput
{
protected void open()
{
}
protected void close()
{
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game.chatbox;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.ScriptID;
import net.runelite.api.VarClientInt;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.vars.InputType;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.input.MouseListener;
import net.runelite.client.input.MouseManager;
import net.runelite.client.input.MouseWheelListener;
@Singleton
@Slf4j
public class ChatboxPanelManager
{
private final Client client;
private final ClientThread clientThread;
private final EventBus eventBus;
private final KeyManager keyManager;
private final MouseManager mouseManager;
private final Provider<ChatboxTextMenuInput> chatboxTextMenuInputProvider;
private final Provider<ChatboxTextInput> chatboxTextInputProvider;
@Getter
private ChatboxInput currentInput = null;
@Inject
private ChatboxPanelManager(EventBus eventBus, Client client, ClientThread clientThread,
KeyManager keyManager, MouseManager mouseManager,
Provider<ChatboxTextMenuInput> chatboxTextMenuInputProvider, Provider<ChatboxTextInput> chatboxTextInputProvider)
{
this.client = client;
this.clientThread = clientThread;
this.eventBus = eventBus;
this.keyManager = keyManager;
this.mouseManager = mouseManager;
this.chatboxTextMenuInputProvider = chatboxTextMenuInputProvider;
this.chatboxTextInputProvider = chatboxTextInputProvider;
}
public void close()
{
clientThread.invokeLater(this::unsafeCloseInput);
}
private void unsafeCloseInput()
{
client.runScript(
ScriptID.RESET_CHATBOX_INPUT,
0,
1
);
}
private void unsafeOpenInput(ChatboxInput input)
{
client.runScript(ScriptID.CLEAR_CHATBOX_PANEL);
eventBus.register(input);
if (input instanceof KeyListener)
{
keyManager.registerKeyListener((KeyListener) input);
}
if (input instanceof MouseListener)
{
mouseManager.registerMouseListener((MouseListener) input);
}
if (input instanceof MouseWheelListener)
{
mouseManager.registerMouseWheelListener((MouseWheelListener) input);
}
currentInput = input;
client.setVar(VarClientInt.INPUT_TYPE, InputType.RUNELITE_CHATBOX_PANEL.getType());
client.getWidget(WidgetInfo.CHATBOX_TITLE).setHidden(true);
client.getWidget(WidgetInfo.CHATBOX_FULL_INPUT).setHidden(true);
Widget c = getContainerWidget();
c.deleteAllChildren();
c.setOnDialogAbortListener((JavaScriptCallback) ev -> this.unsafeCloseInput());
input.open();
}
public void openInput(ChatboxInput input)
{
clientThread.invokeLater(() -> unsafeOpenInput(input));
}
public ChatboxTextMenuInput openTextMenuInput(String title)
{
return chatboxTextMenuInputProvider.get()
.title(title);
}
public ChatboxTextInput openTextInput(String prompt)
{
return chatboxTextInputProvider.get()
.prompt(prompt);
}
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent ev)
{
if (currentInput != null && "resetChatboxInput".equals(ev.getEventName()))
{
try
{
currentInput.close();
}
catch (Exception e)
{
log.warn("Exception closing {}", currentInput.getClass(), e);
}
eventBus.unregister(currentInput);
if (currentInput instanceof KeyListener)
{
keyManager.unregisterKeyListener((KeyListener) currentInput);
}
if (currentInput instanceof MouseListener)
{
mouseManager.unregisterMouseListener((MouseListener) currentInput);
}
if (currentInput instanceof MouseWheelListener)
{
mouseManager.unregisterMouseWheelListener((MouseWheelListener) currentInput);
}
currentInput = null;
}
}
public Widget getContainerWidget()
{
return client.getWidget(WidgetInfo.CHATBOX_CONTAINER);
}
}

View File

@@ -0,0 +1,666 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game.chatbox;
import com.google.inject.Inject;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import javax.swing.SwingUtilities;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.FontTypeFace;
import net.runelite.api.FontID;
import net.runelite.api.WidgetType;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetTextAlignment;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.MouseListener;
@Slf4j
public class ChatboxTextInput extends ChatboxInput implements KeyListener, MouseListener
{
private static final int CURSOR_FLASH_RATE_MILLIS = 1000;
private final ChatboxPanelManager chatboxPanelManager;
private final ClientThread clientThread;
private static IntPredicate getDefaultCharValidator()
{
return i -> i >= 32 && i < 127;
}
@Getter
private String prompt;
private StringBuffer value = new StringBuffer();
@Getter
private int cursor = 0;
@Getter
private int cursorEnd = 0;
private int selectionStart = -1;
private int selectionEnd = -1;
@Getter
private IntPredicate charValidator = getDefaultCharValidator();
@Getter
private Runnable onClose = null;
@Getter
private Consumer<String> onDone = null;
@Getter
private Consumer<String> onChanged = null;
@Getter
private int fontID = FontID.QUILL_8;
// This is a lambda so I can have atomic updates for it's captures
private ToIntFunction<MouseEvent> getCharOffset = null;
private Predicate<MouseEvent> isInBounds = null;
private boolean built = false;
@Inject
protected ChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread)
{
this.chatboxPanelManager = chatboxPanelManager;
this.clientThread = clientThread;
}
public ChatboxTextInput prompt(String prompt)
{
this.prompt = prompt;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public ChatboxTextInput value(String value)
{
this.value = new StringBuffer(value);
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public ChatboxTextInput cursorAt(int index)
{
return cursorAt(index, index);
}
public ChatboxTextInput cursorAt(int indexA, int indexB)
{
if (indexA < 0)
{
indexA = 0;
}
if (indexB < 0)
{
indexB = 0;
}
if (indexA > value.length())
{
indexA = value.length();
}
if (indexB > value.length())
{
indexB = value.length();
}
int start = indexA;
int end = indexB;
if (start > end)
{
int v = start;
start = end;
end = v;
}
this.cursor = start;
this.cursorEnd = end;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public String getValue()
{
return value.toString();
}
public ChatboxTextInput charValidator(IntPredicate val)
{
if (val == null)
{
val = getDefaultCharValidator();
}
this.charValidator = val;
return this;
}
public ChatboxTextInput onClose(Runnable onClose)
{
this.onClose = onClose;
return this;
}
public ChatboxTextInput onDone(Consumer<String> onDone)
{
this.onDone = onDone;
return this;
}
public ChatboxTextInput onChanged(Consumer<String> onChanged)
{
this.onChanged = onChanged;
return this;
}
public ChatboxTextInput fontID(int fontID)
{
this.fontID = fontID;
return this;
}
protected void update()
{
this.built = true;
Widget container = chatboxPanelManager.getContainerWidget();
container.deleteAllChildren();
Widget promptWidget = container.createChild(-1, WidgetType.TEXT);
promptWidget.setText(this.prompt);
promptWidget.setTextColor(0x800000);
promptWidget.setFontId(fontID);
promptWidget.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
promptWidget.setOriginalX(0);
promptWidget.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
promptWidget.setOriginalY(8);
promptWidget.setOriginalHeight(24);
promptWidget.setXTextAlignment(WidgetTextAlignment.CENTER);
promptWidget.setYTextAlignment(WidgetTextAlignment.CENTER);
promptWidget.setWidthMode(WidgetSizeMode.MINUS);
promptWidget.revalidate();
buildEdit(0, 50, container.getWidth(), 0);
}
protected void buildEdit(int x, int y, int w, int h)
{
Widget container = chatboxPanelManager.getContainerWidget();
String lt = value.substring(0, this.cursor);
String mt = value.substring(this.cursor, this.cursorEnd);
String rt = value.substring(this.cursorEnd);
Widget leftText = container.createChild(-1, WidgetType.TEXT);
Widget cursor = container.createChild(-1, WidgetType.RECTANGLE);
Widget middleText = container.createChild(-1, WidgetType.TEXT);
Widget rightText = container.createChild(-1, WidgetType.TEXT);
leftText.setFontId(fontID);
FontTypeFace font = leftText.getFont();
if (h <= 0)
{
h = font.getBaseline();
}
int ltw = font.getTextWidth(lt);
int mtw = font.getTextWidth(mt);
int rtw = font.getTextWidth(rt);
int fullWidth = ltw + mtw + rtw;
int ox = x;
if (w > 0)
{
x += (w - fullWidth) / 2;
}
int ltx = x;
int mtx = ltx + ltw;
int rtx = mtx + mtw;
leftText.setText(lt);
leftText.setOriginalX(ltx);
leftText.setOriginalY(y);
leftText.setOriginalWidth(ltw);
leftText.setOriginalHeight(h);
leftText.revalidate();
if (mt.length() > 0)
{
cursor.setTextColor(0x113399);
}
else
{
cursor.setTextColor(0xFFFFFF);
cursor.setOnTimerListener((JavaScriptCallback) ev ->
{
boolean on = System.currentTimeMillis() % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2);
cursor.setOpacity(on ? 255 : 0);
});
}
cursor.setFilled(true);
cursor.setOriginalX(mtx - 1);
cursor.setOriginalY(y);
cursor.setOriginalWidth(2 + mtw);
cursor.setOriginalHeight(h);
cursor.revalidate();
middleText.setText(mt);
middleText.setFontId(fontID);
middleText.setOriginalX(mtx);
middleText.setOriginalY(y);
middleText.setOriginalWidth(mtw);
middleText.setOriginalHeight(h);
middleText.setTextColor(0xFFFFFF);
middleText.revalidate();
rightText.setText(rt);
rightText.setFontId(fontID);
rightText.setOriginalX(rtx);
rightText.setOriginalY(y);
rightText.setOriginalWidth(rtw);
rightText.setOriginalHeight(h);
rightText.revalidate();
net.runelite.api.Point ccl = container.getCanvasLocation();
int canvasX = ltx + ccl.getX();
Rectangle bounds = new Rectangle(ccl.getX() + ox, ccl.getY() + y, w > 0 ? w : fullWidth, h);
String tsValue = value.toString();
isInBounds = ev -> bounds.contains(ev.getPoint());
getCharOffset = ev ->
{
int cx = ev.getX() - canvasX;
int charIndex = (tsValue.length() * cx) / fullWidth;
// `i` is used to track max execution time incase there is a font with ligature width data that causes this to fail
for (int i = tsValue.length(); i >= 0 && charIndex >= 0 && charIndex <= tsValue.length(); i--)
{
int lcx = charIndex > 0 ? font.getTextWidth(tsValue.substring(0, charIndex - 1)) : 0;
int mcx = font.getTextWidth(tsValue.substring(0, charIndex));
int rcx = charIndex + 1 <= tsValue.length() ? font.getTextWidth(tsValue.substring(0, charIndex + 1)) : mcx;
int leftBound = (lcx + mcx) / 2;
int rightBound = (mcx + rcx) / 2;
if (cx < leftBound)
{
charIndex--;
continue;
}
if (cx > rightBound)
{
charIndex++;
continue;
}
break;
}
if (charIndex < 0)
{
charIndex = 0;
}
if (charIndex > tsValue.length())
{
charIndex = tsValue.length();
}
return charIndex;
};
}
@Override
protected void open()
{
update();
}
@Override
protected void close()
{
if (this.onClose != null)
{
this.onClose.run();
}
}
public ChatboxTextInput build()
{
if (prompt == null)
{
throw new IllegalStateException("prompt must be non-null");
}
chatboxPanelManager.openInput(this);
return this;
}
@Override
public void keyTyped(KeyEvent e)
{
char c = e.getKeyChar();
if (charValidator.test(c))
{
if (cursor != cursorEnd)
{
value.delete(cursor, cursorEnd);
}
value.insert(cursor, c);
cursorAt(cursor + 1);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
}
@Override
public void keyPressed(KeyEvent ev)
{
int code = ev.getKeyCode();
if (ev.isControlDown())
{
switch (code)
{
case KeyEvent.VK_X:
case KeyEvent.VK_C:
if (cursor != cursorEnd)
{
String s = value.substring(cursor, cursorEnd);
if (code == KeyEvent.VK_X)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
}
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.setContents(new StringSelection(s), null);
}
return;
case KeyEvent.VK_V:
try
{
String s = Toolkit.getDefaultToolkit()
.getSystemClipboard()
.getData(DataFlavor.stringFlavor)
.toString();
if (cursor != cursorEnd)
{
value.delete(cursor, cursorEnd);
}
for (int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
if (charValidator.test(ch))
{
value.insert(cursor, ch);
cursor++;
}
}
cursorAt(cursor);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
catch (IOException | UnsupportedFlavorException ex)
{
log.warn("Unable to get clipboard", ex);
}
return;
}
return;
}
int newPos = cursor;
if (ev.isShiftDown())
{
if (selectionEnd == -1 || selectionStart == -1)
{
selectionStart = cursor;
selectionEnd = cursor;
}
newPos = selectionEnd;
}
else
{
selectionStart = -1;
selectionEnd = -1;
}
switch (code)
{
case KeyEvent.VK_DELETE:
if (cursor != cursorEnd)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursor < value.length())
{
value.deleteCharAt(cursor);
cursorAt(cursor);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
return;
case KeyEvent.VK_BACK_SPACE:
if (cursor != cursorEnd)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursor > 0)
{
value.deleteCharAt(cursor - 1);
cursorAt(cursor - 1);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
return;
case KeyEvent.VK_LEFT:
ev.consume();
newPos--;
break;
case KeyEvent.VK_RIGHT:
ev.consume();
newPos++;
break;
case KeyEvent.VK_HOME:
ev.consume();
newPos = 0;
break;
case KeyEvent.VK_END:
ev.consume();
newPos = value.length();
break;
case KeyEvent.VK_ENTER:
ev.consume();
if (onDone != null)
{
onDone.accept(getValue());
}
chatboxPanelManager.close();
return;
case KeyEvent.VK_ESCAPE:
ev.consume();
if (cursor != cursorEnd)
{
cursorAt(cursor);
return;
}
chatboxPanelManager.close();
return;
default:
return;
}
if (newPos > value.length())
{
newPos = value.length();
}
if (newPos < 0)
{
newPos = 0;
}
if (ev.isShiftDown())
{
selectionEnd = newPos;
cursorAt(selectionStart, newPos);
}
else
{
cursorAt(newPos);
}
}
@Override
public void keyReleased(KeyEvent e)
{
}
@Override
public MouseEvent mouseClicked(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mousePressed(MouseEvent mouseEvent)
{
if (mouseEvent.getButton() != MouseEvent.BUTTON1)
{
return mouseEvent;
}
if (isInBounds == null || !isInBounds.test(mouseEvent))
{
if (cursor != cursorEnd)
{
selectionStart = -1;
selectionEnd = -1;
cursorAt(getCharOffset.applyAsInt(mouseEvent));
}
return mouseEvent;
}
int nco = getCharOffset.applyAsInt(mouseEvent);
if (mouseEvent.isShiftDown() && selectionEnd != -1)
{
selectionEnd = nco;
cursorAt(selectionStart, selectionEnd);
}
else
{
selectionStart = nco;
cursorAt(nco);
}
return mouseEvent;
}
@Override
public MouseEvent mouseReleased(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseEntered(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseExited(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseDragged(MouseEvent mouseEvent)
{
if (!SwingUtilities.isLeftMouseButton(mouseEvent))
{
return mouseEvent;
}
int nco = getCharOffset.applyAsInt(mouseEvent);
if (selectionStart != -1)
{
selectionEnd = nco;
cursorAt(selectionStart, selectionEnd);
}
return mouseEvent;
}
@Override
public MouseEvent mouseMoved(MouseEvent mouseEvent)
{
return mouseEvent;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game.chatbox;
import com.google.inject.Inject;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.FontID;
import net.runelite.api.WidgetType;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetTextAlignment;
import net.runelite.client.input.KeyListener;
@Slf4j
public class ChatboxTextMenuInput extends ChatboxInput implements KeyListener
{
@Data
@AllArgsConstructor
private static final class Entry
{
private String text;
private Runnable callback;
}
private final ChatboxPanelManager chatboxPanelManager;
@Getter
private String title;
@Getter
private List<Entry> options = new ArrayList<>();
@Getter
private Runnable onClose;
@Inject
protected ChatboxTextMenuInput(ChatboxPanelManager chatboxPanelManager)
{
this.chatboxPanelManager = chatboxPanelManager;
}
public ChatboxTextMenuInput title(String title)
{
this.title = title;
return this;
}
public ChatboxTextMenuInput option(String text, Runnable callback)
{
options.add(new Entry(text, callback));
return this;
}
public ChatboxTextMenuInput onClose(Runnable onClose)
{
this.onClose = onClose;
return this;
}
public void build()
{
if (title == null)
{
throw new IllegalStateException("Title must be set");
}
if (options.size() < 1)
{
throw new IllegalStateException("You must have atleast 1 option");
}
chatboxPanelManager.openInput(this);
}
@Override
protected void open()
{
Widget container = chatboxPanelManager.getContainerWidget();
Widget prompt = container.createChild(-1, WidgetType.TEXT);
prompt.setText(title);
prompt.setTextColor(0x800000);
prompt.setFontId(FontID.QUILL_8);
prompt.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
prompt.setOriginalX(0);
prompt.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
prompt.setOriginalY(8);
prompt.setOriginalHeight(24);
prompt.setXTextAlignment(WidgetTextAlignment.CENTER);
prompt.setYTextAlignment(WidgetTextAlignment.CENTER);
prompt.setWidthMode(WidgetSizeMode.MINUS);
prompt.revalidate();
int y = prompt.getRelativeX() + prompt.getHeight() + 6;
int height = container.getHeight() - y - 8;
int step = height / options.size();
int maxStep = options.size() >= 3 ? 25 : 30;
if (step > maxStep)
{
int ds = step - maxStep;
step = maxStep;
y += (ds * options.size()) / 2;
}
for (Entry option : options)
{
Widget optWidget = container.createChild(-1, WidgetType.TEXT);
optWidget.setText(option.text);
optWidget.setFontId(FontID.QUILL_8);
optWidget.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
optWidget.setOriginalX(0);
optWidget.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
optWidget.setOriginalY(y);
optWidget.setOriginalHeight(24);
optWidget.setXTextAlignment(WidgetTextAlignment.CENTER);
optWidget.setYTextAlignment(WidgetTextAlignment.CENTER);
optWidget.setWidthMode(WidgetSizeMode.MINUS);
optWidget.setAction(0, "Continue");
optWidget.setOnOpListener((JavaScriptCallback) ev -> callback(option));
optWidget.setOnMouseOverListener((JavaScriptCallback) ev -> optWidget.setTextColor(0xFFFFFF));
optWidget.setOnMouseLeaveListener((JavaScriptCallback) ev -> optWidget.setTextColor(0));
optWidget.setHasListener(true);
optWidget.revalidate();
y += step;
}
}
private void callback(Entry entry)
{
Widget container = chatboxPanelManager.getContainerWidget();
container.setOnKeyListener((Object[]) null);
chatboxPanelManager.close();
entry.callback.run();
}
@Override
protected void close()
{
if (onClose != null)
{
onClose.run();
}
}
@Override
public void keyTyped(KeyEvent e)
{
char c = e.getKeyChar();
if (c == '\033')
{
chatboxPanelManager.close();
e.consume();
return;
}
int n = c - '1';
if (n >= 0 && n < options.size())
{
callback(options.get(n));
e.consume();
}
}
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
{
e.consume();
}
}
@Override
public void keyReleased(KeyEvent e)
{
}
}

View File

@@ -501,7 +501,7 @@ public class TabInterface
{
resetSearch();
clientThread.invokeLater(() -> client.runScript(ScriptID.CLOSE_CHATBOX_INPUT));
clientThread.invokeLater(() -> client.runScript(ScriptID.RESET_CHATBOX_INPUT));
}
else
{
@@ -934,7 +934,7 @@ public class TabInterface
{
// This ensures that any chatbox input (e.g from search) will not remain visible when
// selecting/changing tab
client.runScript(ScriptID.CLOSE_CHATBOX_INPUT);
client.runScript(ScriptID.RESET_CHATBOX_INPUT);
client.setVar(VarClientInt.INPUT_TYPE, inputType.getType());
client.setVar(VarClientStr.INPUT_TEXT, search);

View File

@@ -148,6 +148,7 @@ public class WidgetInfoTableModel extends AbstractTableModel
(w, str) -> w.setTextColor(Integer.parseInt(str, 16)),
String.class
));
out.add(new WidgetField<>("Opacity", Widget::getOpacity, Widget::setOpacity, Integer.class));
out.add(new WidgetField<>("FontId", Widget::getFontId, Widget::setFontId, Integer.class));
out.add(new WidgetField<>("TextShadowed", Widget::getTextShadowed, Widget::setTextShadowed, Boolean.class));
out.add(new WidgetField<>("Name", w -> w.getName().trim(), Widget::setName, String.class));
@@ -156,23 +157,32 @@ public class WidgetInfoTableModel extends AbstractTableModel
out.add(new WidgetField<>("ItemQuantityMode", Widget::getItemQuantityMode, Widget::setItemQuantityMode, Integer.class));
out.add(new WidgetField<>("ModelId", Widget::getModelId));
out.add(new WidgetField<>("SpriteId", Widget::getSpriteId, Widget::setSpriteId, Integer.class));
out.add(new WidgetField<>("Width", Widget::getWidth, Widget::setWidth, Integer.class));
out.add(new WidgetField<>("Height", Widget::getHeight, Widget::setHeight, Integer.class));
out.add(new WidgetField<>("BorderType", Widget::getBorderType, Widget::setBorderType, Integer.class));
out.add(new WidgetField<>("IsIf3", Widget::isIf3));
out.add(new WidgetField<>("HasListener", Widget::hasListener, Widget::setHasListener, Boolean.class));
out.add(new WidgetField<>("Filled", Widget::isFilled, Widget::setFilled, Boolean.class));
out.add(new WidgetField<>("OriginalX", Widget::getOriginalX, Widget::setOriginalX, Integer.class));
out.add(new WidgetField<>("OriginalY", Widget::getOriginalY, Widget::setOriginalY, Integer.class));
out.add(new WidgetField<>("OriginalWidth", Widget::getOriginalWidth, Widget::setOriginalWidth, Integer.class));
out.add(new WidgetField<>("OriginalHeight", Widget::getOriginalHeight, Widget::setOriginalHeight, Integer.class));
out.add(new WidgetField<>("XPositionMode", Widget::getXPositionMode, Widget::setXPositionMode, Integer.class));
out.add(new WidgetField<>("YPositionMode", Widget::getYPositionMode, Widget::setYPositionMode, Integer.class));
out.add(new WidgetField<>("WidthMode", Widget::getWidthMode, Widget::setWidthMode, Integer.class));
out.add(new WidgetField<>("HeightMode", Widget::getHeightMode, Widget::setHeightMode, Integer.class));
out.add(new WidgetField<>("XTextAlignment", Widget::getXTextAlignment, Widget::setXTextAlignment, Integer.class));
out.add(new WidgetField<>("YTextAlignment", Widget::getYTextAlignment, Widget::setYTextAlignment, Integer.class));
out.add(new WidgetField<>("RelativeX", Widget::getRelativeX, Widget::setRelativeX, Integer.class));
out.add(new WidgetField<>("RelativeY", Widget::getRelativeY, Widget::setRelativeY, Integer.class));
out.add(new WidgetField<>("Width", Widget::getWidth, Widget::setWidth, Integer.class));
out.add(new WidgetField<>("Height", Widget::getHeight, Widget::setHeight, Integer.class));
out.add(new WidgetField<>("CanvasLocation", Widget::getCanvasLocation));
out.add(new WidgetField<>("Bounds", Widget::getBounds));
out.add(new WidgetField<>("ScrollX", Widget::getScrollX, Widget::setScrollX, Integer.class));
out.add(new WidgetField<>("ScrollY", Widget::getScrollY, Widget::setScrollY, Integer.class));
out.add(new WidgetField<>("ScrollWidth", Widget::getScrollWidth, Widget::setScrollWidth, Integer.class));
out.add(new WidgetField<>("ScrollHeight", Widget::getScrollHeight, Widget::setScrollHeight, Integer.class));
out.add(new WidgetField<>("OriginalX", Widget::getOriginalX));
out.add(new WidgetField<>("OriginalY", Widget::getOriginalY));
out.add(new WidgetField<>("BorderType", Widget::getBorderType, Widget::setBorderType, Integer.class));
out.add(new WidgetField<>("DragDeadZone", Widget::getDragDeadZone, Widget::setDragDeadZone, Integer.class));
out.add(new WidgetField<>("DragDeadTime", Widget::getDragDeadTime, Widget::setDragDeadTime, Integer.class));
out.add(new WidgetField<>("IsIf3", Widget::isIf3));
out.add(new WidgetField<>("HasListener", Widget::hasListener, Widget::setHasListener, Boolean.class));
return out;
}

View File

@@ -142,6 +142,17 @@ class WidgetInspector extends JFrame
onConfigChanged(null);
bottomPanel.add(alwaysOnTop);
final JButton revalidateWidget = new JButton("Revalidate");
revalidateWidget.addActionListener(ev -> clientThread.invokeLater(() ->
{
if (plugin.currentWidget == null)
{
return;
}
plugin.currentWidget.revalidate();
}));
bottomPanel.add(revalidateWidget);
final JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScrollPane, infoScrollPane);
add(split, BorderLayout.CENTER);