v0.9.4: overhauled text input, now multiplatform using libGDX textField
This commit is contained in:
@@ -37,7 +37,7 @@ public class Script extends Program {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized static<T extends Script> T use( Class<T> c ) {
|
public synchronized static<T extends Script> T use( Class<T> c ) {
|
||||||
|
|
||||||
if (c != curScriptClass) {
|
if (c != curScriptClass) {
|
||||||
|
|
||||||
Script script = all.get( c );
|
Script script = all.get( c );
|
||||||
@@ -45,11 +45,7 @@ public class Script extends Program {
|
|||||||
script = Reflection.newInstance( c );
|
script = Reflection.newInstance( c );
|
||||||
all.put( c, script );
|
all.put( c, script );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curScript != null) {
|
|
||||||
curScript.unuse();
|
|
||||||
}
|
|
||||||
|
|
||||||
curScript = script;
|
curScript = script;
|
||||||
curScriptClass = c;
|
curScriptClass = c;
|
||||||
curScript.use();
|
curScript.use();
|
||||||
@@ -58,6 +54,11 @@ public class Script extends Program {
|
|||||||
|
|
||||||
return (T)curScript;
|
return (T)curScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized static void unuse(){
|
||||||
|
curScript = null;
|
||||||
|
curScriptClass = null;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized static void reset() {
|
public synchronized static void reset() {
|
||||||
for (Script script:all.values()) {
|
for (Script script:all.values()) {
|
||||||
@@ -77,7 +78,5 @@ public class Script extends Program {
|
|||||||
link();
|
link();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unuse() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
149
SPD-classes/src/main/java/com/watabou/noosa/TextInput.java
Normal file
149
SPD-classes/src/main/java/com/watabou/noosa/TextInput.java
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package com.watabou.noosa;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Files;
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Stage;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Container;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextArea;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
||||||
|
import com.badlogic.gdx.utils.Align;
|
||||||
|
import com.badlogic.gdx.utils.viewport.ScreenViewport;
|
||||||
|
import com.watabou.glscripts.Script;
|
||||||
|
import com.watabou.glwrap.Blending;
|
||||||
|
import com.watabou.glwrap.Quad;
|
||||||
|
import com.watabou.glwrap.Texture;
|
||||||
|
import com.watabou.noosa.ui.Component;
|
||||||
|
import com.watabou.utils.FileUtils;
|
||||||
|
import com.watabou.utils.Point;
|
||||||
|
|
||||||
|
//essentially contains a libGDX text input field, plus a PD-rendered background
|
||||||
|
public class TextInput extends Component {
|
||||||
|
|
||||||
|
private Stage stage;
|
||||||
|
private Container container;
|
||||||
|
private TextField textField;
|
||||||
|
|
||||||
|
private Skin skin;
|
||||||
|
|
||||||
|
private NinePatch bg;
|
||||||
|
|
||||||
|
public TextInput( NinePatch bg, boolean multiline ){
|
||||||
|
super();
|
||||||
|
this.bg = bg;
|
||||||
|
add(bg);
|
||||||
|
|
||||||
|
stage = new Stage(new ScreenViewport());
|
||||||
|
Game.inputHandler.addInputProcessor(stage);
|
||||||
|
|
||||||
|
container = new Container<TextField>();
|
||||||
|
stage.addActor(container);
|
||||||
|
container.setTransform(true);
|
||||||
|
|
||||||
|
skin = new Skin(FileUtils.getFileHandle(Files.FileType.Internal, "gdx/textfield.json"));
|
||||||
|
|
||||||
|
int zoom = (int) Camera.main.zoom;
|
||||||
|
int textSize = multiline ? 6 : 9;
|
||||||
|
TextField.TextFieldStyle style = skin.get(TextField.TextFieldStyle.class);
|
||||||
|
style.font = Game.platform.getFont(textSize*zoom, "", false, false);
|
||||||
|
style.background = null;
|
||||||
|
textField = multiline ? new TextArea("", style) : new TextField("", style);
|
||||||
|
textField.setProgrammaticChangeEvents(true);
|
||||||
|
|
||||||
|
if (!multiline) textField.setAlignment(Align.center);
|
||||||
|
|
||||||
|
textField.addListener(new ChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void changed(ChangeEvent event, Actor actor) {
|
||||||
|
BitmapFont f = Game.platform.getFont(textSize*zoom, textField.getText(), false, false);
|
||||||
|
TextField.TextFieldStyle style = textField.getStyle();
|
||||||
|
if (f != style.font){
|
||||||
|
style.font = f;
|
||||||
|
textField.setStyle(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
container.setActor(textField);
|
||||||
|
stage.setKeyboardFocus(textField);
|
||||||
|
Gdx.input.setOnscreenKeyboardVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text){
|
||||||
|
textField.setText(text);
|
||||||
|
textField.setCursorPosition(textField.getText().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLength(int maxLength){
|
||||||
|
textField.setMaxLength(maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText(){
|
||||||
|
return textField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layout() {
|
||||||
|
super.layout();
|
||||||
|
|
||||||
|
float contX = x;
|
||||||
|
float contY = y;
|
||||||
|
float contW = width;
|
||||||
|
float contH = height;
|
||||||
|
|
||||||
|
if (bg != null){
|
||||||
|
bg.x = x;
|
||||||
|
bg.y = y;
|
||||||
|
bg.size(width, height);
|
||||||
|
|
||||||
|
contX += bg.marginLeft();
|
||||||
|
contY += bg.marginTop();
|
||||||
|
contW -= bg.marginHor();
|
||||||
|
contH -= bg.marginVer();
|
||||||
|
}
|
||||||
|
|
||||||
|
float zoom = Camera.main.zoom;
|
||||||
|
Camera c = camera();
|
||||||
|
if (c != null){
|
||||||
|
zoom = c.zoom;
|
||||||
|
Point p = c.cameraToScreen(contX, contY);
|
||||||
|
contX = p.x/zoom;
|
||||||
|
contY = p.y/zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.align(Align.topLeft);
|
||||||
|
container.setPosition(contX*zoom, (Game.height-(contY*zoom)));
|
||||||
|
container.size(contW*zoom, contH*zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
super.update();
|
||||||
|
stage.act(Game.elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
super.draw();
|
||||||
|
Quad.releaseIndices();
|
||||||
|
Script.unuse();
|
||||||
|
Texture.clear();
|
||||||
|
stage.draw();
|
||||||
|
Quad.bindIndices();
|
||||||
|
Blending.useDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void destroy() {
|
||||||
|
super.destroy();
|
||||||
|
if (stage != null) {
|
||||||
|
stage.dispose();
|
||||||
|
skin.dispose();
|
||||||
|
Game.inputHandler.removeInputProcessor(stage);
|
||||||
|
Gdx.input.setOnscreenKeyboardVisible(false);
|
||||||
|
Game.platform.updateSystemUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,15 +37,6 @@ public abstract class PlatformSupport {
|
|||||||
public abstract void updateSystemUI();
|
public abstract void updateSystemUI();
|
||||||
|
|
||||||
public abstract boolean connectedToUnmeteredNetwork();
|
public abstract boolean connectedToUnmeteredNetwork();
|
||||||
|
|
||||||
//FIXME this is currently used because no platform-agnostic text input has been implemented.
|
|
||||||
//should look into doing that using either plain openGL or libgdx's libraries
|
|
||||||
public abstract void promptTextInput( String title, String hintText, int maxLen, boolean multiLine,
|
|
||||||
String posTxt, String negTxt, TextCallback callback);
|
|
||||||
|
|
||||||
public static abstract class TextCallback {
|
|
||||||
public abstract void onSelect( boolean positive, String text );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void vibrate( int millis ){
|
public void vibrate( int millis ){
|
||||||
//regular GDX vibration by default
|
//regular GDX vibration by default
|
||||||
|
|||||||
@@ -160,22 +160,6 @@ public class AndroidPlatformSupport extends PlatformSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void promptTextInput(final String title, final String hintText, final int maxLen, final boolean multiLine, final String posTxt, final String negTxt, final TextCallback callback) {
|
|
||||||
Game.runOnRenderThread( new Callback() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
Game.scene().addToFront(new WndAndroidTextInput(title, hintText, maxLen, multiLine, posTxt, negTxt) {
|
|
||||||
@Override
|
|
||||||
protected void onSelect(boolean positive) {
|
|
||||||
callback.onSelect(positive, getText());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FONT SUPPORT */
|
/* FONT SUPPORT */
|
||||||
|
|
||||||
//droid sans / roboto, or a custom pixel font, for use with Latin and Cyrillic languages
|
//droid sans / roboto, or a custom pixel font, for use with Latin and Cyrillic languages
|
||||||
|
|||||||
21
core/src/main/assets/gdx/textfield.atlas
Normal file
21
core/src/main/assets/gdx/textfield.atlas
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
textfield.png
|
||||||
|
size: 4,4
|
||||||
|
format: RGBA8888
|
||||||
|
filter: Nearest,Nearest
|
||||||
|
repeat: none
|
||||||
|
cursor
|
||||||
|
rotate: false
|
||||||
|
xy: 0, 0
|
||||||
|
size: 3, 3
|
||||||
|
split: 1, 1, 1, 1
|
||||||
|
orig: 3, 3
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
selection
|
||||||
|
rotate: false
|
||||||
|
xy: 3, 3
|
||||||
|
size: 1, 1
|
||||||
|
orig: 1, 1
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
8
core/src/main/assets/gdx/textfield.json
Normal file
8
core/src/main/assets/gdx/textfield.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
Color: {
|
||||||
|
black: { a: 1, b: 0, g: 0, r: 0 },
|
||||||
|
},
|
||||||
|
TextFieldStyle: {
|
||||||
|
default: { selection: selection, fontColor: black, cursor: cursor }
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
core/src/main/assets/gdx/textfield.png
Normal file
BIN
core/src/main/assets/gdx/textfield.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -28,6 +28,7 @@ public class Chrome {
|
|||||||
public enum Type {
|
public enum Type {
|
||||||
TOAST,
|
TOAST,
|
||||||
TOAST_TR,
|
TOAST_TR,
|
||||||
|
TOAST_WHITE,
|
||||||
WINDOW,
|
WINDOW,
|
||||||
WINDOW_SILVER,
|
WINDOW_SILVER,
|
||||||
RED_BUTTON,
|
RED_BUTTON,
|
||||||
@@ -53,10 +54,12 @@ public class Chrome {
|
|||||||
case TOAST_TR:
|
case TOAST_TR:
|
||||||
case GREY_BUTTON_TR:
|
case GREY_BUTTON_TR:
|
||||||
return new NinePatch( Asset, 20, 9, 9, 9, 4 );
|
return new NinePatch( Asset, 20, 9, 9, 9, 4 );
|
||||||
|
case TOAST_WHITE:
|
||||||
|
return new NinePatch( Asset, 29, 0, 9, 9, 4 );
|
||||||
case RED_BUTTON:
|
case RED_BUTTON:
|
||||||
return new NinePatch( Asset, 29, 0, 6, 6, 2 );
|
return new NinePatch( Asset, 38, 0, 6, 6, 2 );
|
||||||
case GREY_BUTTON:
|
case GREY_BUTTON:
|
||||||
return new NinePatch( Asset, 29, 6, 6, 6, 2 );
|
return new NinePatch( Asset, 38, 6, 6, 6, 2 );
|
||||||
case TAG:
|
case TAG:
|
||||||
return new NinePatch( Asset, 22, 18, 16, 14, 3 );
|
return new NinePatch( Asset, 22, 18, 16, 14, 3 );
|
||||||
case GEM:
|
case GEM:
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.shatteredpixel.shatteredpixeldungeon.ui;
|
||||||
|
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.Chrome;
|
||||||
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
|
||||||
|
import com.watabou.noosa.TextInput;
|
||||||
|
import com.watabou.utils.DeviceCompat;
|
||||||
|
|
||||||
|
public class WndTextInput extends Window {
|
||||||
|
|
||||||
|
private static final int WIDTH = 120;
|
||||||
|
private static final int W_LAND_MULTI = 200; //in the specific case of multiline in landscape
|
||||||
|
private static final int MARGIN = 2;
|
||||||
|
private static final int BUTTON_HEIGHT = 16;
|
||||||
|
|
||||||
|
public WndTextInput(final String title, final String initialValue, final int maxLength,
|
||||||
|
final boolean multiLine, final String posTxt, final String negTxt) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
//need to offset to give space for the soft keyboard
|
||||||
|
if (!DeviceCompat.isDesktop()) {
|
||||||
|
if (PixelScene.landscape()) {
|
||||||
|
offset(-45);
|
||||||
|
} else {
|
||||||
|
offset(multiLine ? -60 : -45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int width;
|
||||||
|
if (PixelScene.landscape() && multiLine) {
|
||||||
|
width = W_LAND_MULTI; //more editing space for landscape users
|
||||||
|
} else {
|
||||||
|
width = WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
final RenderedTextBlock txtTitle = PixelScene.renderTextBlock(title, 9);
|
||||||
|
txtTitle.maxWidth(width);
|
||||||
|
txtTitle.hardlight(Window.TITLE_COLOR);
|
||||||
|
txtTitle.setPos((width - txtTitle.width()) / 2, 2);
|
||||||
|
add(txtTitle);
|
||||||
|
|
||||||
|
TextInput textBox = new TextInput(Chrome.get(Chrome.Type.TOAST_WHITE), multiLine);
|
||||||
|
if (initialValue != null) textBox.setText(initialValue);
|
||||||
|
textBox.setMaxLength(maxLength);
|
||||||
|
|
||||||
|
float pos = txtTitle.bottom() + 2 * MARGIN;
|
||||||
|
|
||||||
|
//sets different height depending on whether this is a single or multi line input.
|
||||||
|
final float inputHeight;
|
||||||
|
if (multiLine) {
|
||||||
|
inputHeight = 64; //~8 lines of text
|
||||||
|
} else {
|
||||||
|
inputHeight = 16;
|
||||||
|
}
|
||||||
|
add(textBox);
|
||||||
|
textBox.setRect(MARGIN, pos, width-2*MARGIN, inputHeight);
|
||||||
|
|
||||||
|
pos += inputHeight + MARGIN;
|
||||||
|
|
||||||
|
final RedButton positiveBtn = new RedButton(posTxt) {
|
||||||
|
@Override
|
||||||
|
protected void onClick() {
|
||||||
|
onSelect(true, textBox.getText());
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final RedButton negativeBtn;
|
||||||
|
if (negTxt != null) {
|
||||||
|
negativeBtn = new RedButton(negTxt) {
|
||||||
|
@Override
|
||||||
|
protected void onClick() {
|
||||||
|
onSelect(false, textBox.getText());
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
negativeBtn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negTxt != null) {
|
||||||
|
positiveBtn.setRect(MARGIN, pos, (width - MARGIN * 3) / 2, BUTTON_HEIGHT);
|
||||||
|
add(positiveBtn);
|
||||||
|
negativeBtn.setRect(positiveBtn.right() + MARGIN, pos, (width - MARGIN * 3) / 2, BUTTON_HEIGHT);
|
||||||
|
add(negativeBtn);
|
||||||
|
} else {
|
||||||
|
positiveBtn.setRect(MARGIN, pos, width - MARGIN * 2, BUTTON_HEIGHT);
|
||||||
|
add(positiveBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += BUTTON_HEIGHT + MARGIN;
|
||||||
|
|
||||||
|
//need to resize first before laying out the text box, as it depends on the window's camera
|
||||||
|
resize(width, (int) pos);
|
||||||
|
|
||||||
|
textBox.setRect(MARGIN, textBox.top(), width-2*MARGIN, inputHeight);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSelect(boolean positive, String text){ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
//Do nothing, prevents accidentally losing writing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,21 +66,6 @@ public class DesktopPlatformSupport extends PlatformSupport {
|
|||||||
public boolean connectedToUnmeteredNetwork() {
|
public boolean connectedToUnmeteredNetwork() {
|
||||||
return true; //no easy way to check this in desktop, just assume user doesn't care
|
return true; //no easy way to check this in desktop, just assume user doesn't care
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
//FIXME tinyfd_inputBox isn't a full solution for this. No support for multiline, looks ugly. Ideally we'd have an opengl-based input box
|
|
||||||
public void promptTextInput(String title, String hintText, int maxLen, boolean multiLine, String posTxt, String negTxt, TextCallback callback) {
|
|
||||||
String result = TinyFileDialogs.tinyfd_inputBox(title, title, hintText);
|
|
||||||
if (result == null){
|
|
||||||
callback.onSelect(false, "");
|
|
||||||
} else {
|
|
||||||
if (result.contains("\r\n")) result = result.substring(0, result.indexOf("\r\n"));
|
|
||||||
if (result.contains("\n")) result = result.substring(0, result.indexOf("\n"));
|
|
||||||
if (result.length() > maxLen) result = result.substring(0, maxLen);
|
|
||||||
callback.onSelect(true, result.replace("\r\n", "").replace("\n", ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FONT SUPPORT */
|
/* FONT SUPPORT */
|
||||||
|
|
||||||
//custom pixel font, for use with Latin and Cyrillic languages
|
//custom pixel font, for use with Latin and Cyrillic languages
|
||||||
|
|||||||
@@ -56,11 +56,6 @@ public class IOSPlatformSupport extends PlatformSupport {
|
|||||||
return !test.getFlags().contains(SCNetworkReachabilityFlags.IsWWAN);
|
return !test.getFlags().contains(SCNetworkReachabilityFlags.IsWWAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void promptTextInput(String title, String hintText, int maxLen, boolean multiLine, String posTxt, String negTxt, TextCallback callback) {
|
|
||||||
//TODO need multiplat text input, this does nothing atm!
|
|
||||||
}
|
|
||||||
|
|
||||||
public void vibrate( int millis ){
|
public void vibrate( int millis ){
|
||||||
//gives a short vibrate on iPhone 6+, no vibration otherwise
|
//gives a short vibrate on iPhone 6+, no vibration otherwise
|
||||||
AudioServices.playSystemSound(1520);
|
AudioServices.playSystemSound(1520);
|
||||||
|
|||||||
Reference in New Issue
Block a user