/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2025 Evan Debenham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*/
package com.watabou.noosa;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
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.Viewport;
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.DeviceCompat;
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, int size ){
super();
this.bg = bg;
add(bg);
//use a custom viewport here to ensure stage camera matches game camera
Viewport viewport = new Viewport() {};
viewport.setWorldSize(Game.width, Game.height);
viewport.setScreenBounds(0, 0, Game.width, Game.height);
viewport.setCamera(new OrthographicCamera());
//TODO this is needed for the moment as Spritebatch switched to using VAOs in libGDX v1.13.1
// This results in HARD crashes atm, whereas old vertex arrays work fine
SpriteBatch.overrideVertexType = Mesh.VertexDataType.VertexArray;
stage = new Stage(viewport);
Game.inputHandler.addInputProcessor(stage);
container = new Container();
stage.addActor(container);
container.setTransform(true);
skin = new Skin(FileUtils.getFileHandle(Files.FileType.Internal, "gdx/textfield.json"));
TextField.TextFieldStyle style = skin.get(TextField.TextFieldStyle.class);
style.font = Game.platform.getFont(size, "", false, false);
style.background = null;
if (multiline){
textField = new TextArea("", style){
@Override
public void cut() {
super.cut();
onClipBoardUpdate();
}
@Override
public void copy() {
super.copy();
onClipBoardUpdate();
}
};
} else {
textField = new TextField("", style){
@Override
public void cut() {
super.cut();
onClipBoardUpdate();
}
@Override
public void copy() {
super.copy();
onClipBoardUpdate();
}
};
}
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(size, textField.getText(), false, false);
TextField.TextFieldStyle style = textField.getStyle();
if (f != style.font){
style.font = f;
textField.setStyle(style);
}
onChanged();
}
});
if (!multiline){
textField.setTextFieldListener(new TextField.TextFieldListener(){
public void keyTyped (TextField textField, char c){
if (c == '\r' || c == '\n'){
enterPressed();
}
}
});
}
textField.setOnscreenKeyboard(new TextField.OnscreenKeyboard() {
@Override
public void show(boolean visible) {
Game.platform.setOnscreenKeyboardVisible(visible, multiline);
}
});
container.setActor(textField);
stage.setKeyboardFocus(textField);
Game.platform.setOnscreenKeyboardVisible(true, multiline);
}
public void enterPressed(){
//fires any time enter is pressed, do nothing by default
};
public void onChanged(){
//fires any time the text box is changed, do nothing by default
}
public void onClipBoardUpdate(){
//fires any time the clipboard is updated via cut or copy, do nothing by default
}
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();
}
public void copyToClipboard(){
if (textField.getSelection().isEmpty()) {
textField.selectAll();
}
textField.copy();
}
public void pasteFromClipboard(){
String contents = Gdx.app.getClipboard().getContents();
if (contents == null) return;
if (!textField.getSelection().isEmpty()){
//just use cut, but override clipboard
textField.cut();
Gdx.app.getClipboard().setContents(contents);
}
String existing = textField.getText();
int cursorIdx = textField.getCursorPosition();
textField.setText(existing.substring(0, cursorIdx) + contents + existing.substring(cursorIdx));
textField.setCursorPosition(cursorIdx + contents.length());
}
@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);
Game.platform.setOnscreenKeyboardVisible(false, false);
if (!DeviceCompat.isDesktop()) Game.platform.updateSystemUI();
}
}
}