v0.9.4: overhauled text input, now multiplatform using libGDX textField

This commit is contained in:
Evan Debenham 2021-07-08 12:38:45 -04:00
parent bcdbf8ef43
commit 7307c38a3c
12 changed files with 296 additions and 56 deletions

View File

@ -37,7 +37,7 @@ public class Script extends Program {
@SuppressWarnings("unchecked")
public synchronized static<T extends Script> T use( Class<T> c ) {
if (c != curScriptClass) {
Script script = all.get( c );
@ -45,11 +45,7 @@ public class Script extends Program {
script = Reflection.newInstance( c );
all.put( c, script );
}
if (curScript != null) {
curScript.unuse();
}
curScript = script;
curScriptClass = c;
curScript.use();
@ -58,6 +54,11 @@ public class Script extends Program {
return (T)curScript;
}
public synchronized static void unuse(){
curScript = null;
curScriptClass = null;
}
public synchronized static void reset() {
for (Script script:all.values()) {
@ -77,7 +78,5 @@ public class Script extends Program {
link();
}
public void unuse() {
}
}

View 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();
}
}
}

View File

@ -37,15 +37,6 @@ public abstract class PlatformSupport {
public abstract void updateSystemUI();
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 ){
//regular GDX vibration by default

View File

@ -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 */
//droid sans / roboto, or a custom pixel font, for use with Latin and Cyrillic languages

View 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

View File

@ -0,0 +1,8 @@
{
Color: {
black: { a: 1, b: 0, g: 0, r: 0 },
},
TextFieldStyle: {
default: { selection: selection, fontColor: black, cursor: cursor }
}
}

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

View File

@ -28,6 +28,7 @@ public class Chrome {
public enum Type {
TOAST,
TOAST_TR,
TOAST_WHITE,
WINDOW,
WINDOW_SILVER,
RED_BUTTON,
@ -53,10 +54,12 @@ public class Chrome {
case TOAST_TR:
case GREY_BUTTON_TR:
return new NinePatch( Asset, 20, 9, 9, 9, 4 );
case TOAST_WHITE:
return new NinePatch( Asset, 29, 0, 9, 9, 4 );
case RED_BUTTON:
return new NinePatch( Asset, 29, 0, 6, 6, 2 );
return new NinePatch( Asset, 38, 0, 6, 6, 2 );
case GREY_BUTTON:
return new NinePatch( Asset, 29, 6, 6, 6, 2 );
return new NinePatch( Asset, 38, 6, 6, 6, 2 );
case TAG:
return new NinePatch( Asset, 22, 18, 16, 14, 3 );
case GEM:

View File

@ -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
}
}

View File

@ -66,21 +66,6 @@ public class DesktopPlatformSupport extends PlatformSupport {
public boolean connectedToUnmeteredNetwork() {
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 */
//custom pixel font, for use with Latin and Cyrillic languages

View File

@ -56,11 +56,6 @@ public class IOSPlatformSupport extends PlatformSupport {
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 ){
//gives a short vibrate on iPhone 6+, no vibration otherwise
AudioServices.playSystemSound(1520);