v2.5.0: added functionality for custom note! Only plain text notes atm

This commit is contained in:
Evan Debenham
2024-07-02 13:05:38 -04:00
parent e8ea9da26e
commit 8e99b51cf9
5 changed files with 366 additions and 23 deletions
@@ -1,3 +1,13 @@
ui.customnotebutton.hover_text=Add a Custom Note
ui.customnotebutton.default_title_text=Custom Text Note
ui.customnotebutton$customnotewindow.edit_title=Edit Title
ui.customnotebutton$customnotewindow.add_text=Add Text
ui.customnotebutton$customnotewindow.edit_text=Edit Text
ui.customnotebutton$customnotewindow.delete=Delete
ui.customnotebutton$customnotewindow.delete_warn=Are you sure you want to delete this custom note?
ui.customnotebutton$customnotewindow.confirm=Confirm
ui.customnotebutton$customnotewindow.cancel=Cancel
ui.quickslotbutton.select_item=Quickslot an item ui.quickslotbutton.select_item=Quickslot an item
ui.talentspane.tier=tier %d ui.talentspane.tier=tier %d
@@ -117,6 +117,7 @@ windows.wndjournal$alchemytab.title=Alchemy Guide
windows.wndjournal$guidetab.missing=page missing windows.wndjournal$guidetab.missing=page missing
windows.wndjournal$notestab.title=Adventuring Notes windows.wndjournal$notestab.title=Adventuring Notes
windows.wndjournal$notestab.desc=As you journey through the dungeon, you will automatically record noteworthy things here. windows.wndjournal$notestab.desc=As you journey through the dungeon, you will automatically record noteworthy things here.
windows.wndjournal$notestab.custom_notes=Custom Notes
windows.wndjournal$notestab.floor_header=Floor %d windows.wndjournal$notestab.floor_header=Floor %d
windows.wndjournal$catalogtab.title=Catalogs windows.wndjournal$catalogtab.title=Catalogs
windows.wndjournal$catalogtab.title_equipment=Equipment windows.wndjournal$catalogtab.title_equipment=Equipment
@@ -36,6 +36,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.RatKing;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Shopkeeper; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Shopkeeper;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker;
import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Generator;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key; import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.WeakFloorRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.WeakFloorRoom;
@@ -78,6 +79,8 @@ public class Notes {
public int quantity() { return 1; } public int quantity() { return 1; }
protected abstract int order();
public abstract String title(); public abstract String title();
public abstract String desc(); public abstract String desc();
@@ -243,6 +246,11 @@ public class Notes {
} }
} }
@Override
protected int order(){
return landmark.ordinal();
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return (obj instanceof LandmarkRecord) return (obj instanceof LandmarkRecord)
@@ -310,6 +318,11 @@ public class Notes {
return key.getClass(); return key.getClass();
} }
@Override
protected int order() {
return 1000 + Generator.Category.order(key);
}
public int quantity(){ public int quantity(){
return key.quantity(); return key.quantity();
} }
@@ -339,20 +352,147 @@ public class Notes {
} }
} }
public enum CustomType {
TEXT,
DEPTH, //TODO
ITEM, //TODO
ITEM_TYPE //TODO
}
public static class CustomRecord extends Record {
protected CustomType type;
protected int ID;
protected Class itemClass;
protected String title;
protected String body;
public CustomRecord() {}
public CustomRecord(String title, String desc) {
type = CustomType.TEXT;
this.title = title;
body = desc;
}
public CustomRecord(int depth, String title, String desc) {
type = CustomType.DEPTH;
this.depth = depth;
this.title = title;
body = desc;
}
public CustomRecord(Item item, String title, String desc) {
type = CustomType.ITEM;
itemClass = item.getClass();
this.title = title;
body = desc;
}
@Override
public int depth() {
if (type == CustomType.DEPTH){
return depth;
} else {
return 0;
}
}
@Override
public Image icon() {
switch (type){
case TEXT: default:
return Icons.SCROLL_COLOR.get();
case DEPTH:
return Icons.STAIRS.get();
}
}
@Override
public Visual secondIcon() {
switch (type){
case TEXT: default:
//TODO perhaps use first few chars from title?
return null;
case DEPTH:
BitmapText text = new BitmapText(Integer.toString(depth()), PixelScene.pixelFont);
text.measure();
return text;
}
}
@Override
protected int order() {
return 2000 + ID;
}
public void editText(String title, String desc){
this.title = title;
this.body = desc;
}
@Override
public String title() {
return title;
}
@Override
public String desc() {
return body;
}
@Override
public boolean equals(Object obj) {
return obj instanceof CustomRecord && ((CustomRecord) obj).ID == ID;
}
private static final String TYPE = "type";
private static final String ID_NUMBER = "id_number";
private static final String TITLE = "title";
private static final String BODY = "body";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(TYPE, type);
bundle.put(ID_NUMBER, ID);
bundle.put(TITLE, title);
bundle.put(BODY, body);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
type = bundle.getEnum(TYPE, CustomType.class);
ID = bundle.getInt(ID_NUMBER);
title = bundle.getString(TITLE);
body = bundle.getString(BODY);
}
}
private static ArrayList<Record> records; private static ArrayList<Record> records;
public static void reset() { public static void reset() {
records = new ArrayList<>(); records = new ArrayList<>();
} }
private static final String RECORDS = "records"; private static final String RECORDS = "records";
private static final String NEXT_CUSTOM_ID = "next_custom_id";
protected static int nextCustomID = 0;
public static void storeInBundle( Bundle bundle ) { public static void storeInBundle( Bundle bundle ) {
bundle.put( RECORDS, records ); bundle.put( RECORDS, records );
bundle.put( NEXT_CUSTOM_ID, nextCustomID );
} }
public static void restoreFromBundle( Bundle bundle ) { public static void restoreFromBundle( Bundle bundle ) {
records = new ArrayList<>(); records = new ArrayList<>();
nextCustomID = bundle.getInt( NEXT_CUSTOM_ID );
for (Bundlable rec : bundle.getCollection( RECORDS ) ) { for (Bundlable rec : bundle.getCollection( RECORDS ) ) {
records.add( (Record) rec ); records.add( (Record) rec );
} }
@@ -362,7 +502,7 @@ public class Notes {
LandmarkRecord l = new LandmarkRecord( landmark, Dungeon.depth ); LandmarkRecord l = new LandmarkRecord( landmark, Dungeon.depth );
if (!records.contains(l)) { if (!records.contains(l)) {
boolean result = records.add(new LandmarkRecord(landmark, Dungeon.depth)); boolean result = records.add(new LandmarkRecord(landmark, Dungeon.depth));
Collections.sort(records); Collections.sort(records, comparator);
return result; return result;
} }
return false; return false;
@@ -380,7 +520,7 @@ public class Notes {
KeyRecord k = new KeyRecord(key); KeyRecord k = new KeyRecord(key);
if (!records.contains(k)){ if (!records.contains(k)){
boolean result = records.add(k); boolean result = records.add(k);
Collections.sort(records); Collections.sort(records, comparator);
return result; return result;
} else { } else {
k = (KeyRecord) records.get(records.indexOf(k)); k = (KeyRecord) records.get(records.indexOf(k));
@@ -413,8 +553,22 @@ public class Notes {
} }
} }
public static ArrayList<Record> getRecords(){ public static boolean add( CustomRecord rec ){
return getRecords(Record.class); rec.ID = nextCustomID++;
if (!records.contains(rec)){
boolean result = records.add(rec);
Collections.sort(records, comparator);
return result;
}
return false;
}
public static boolean remove( CustomRecord rec ){
if (records.contains(rec)){
records.remove(rec);
return true;
}
return false;
} }
public static <T extends Record> ArrayList<T> getRecords( Class<T> recordType ){ public static <T extends Record> ArrayList<T> getRecords( Class<T> recordType ){
@@ -443,23 +597,8 @@ public class Notes {
private static final Comparator<Record> comparator = new Comparator<Record>() { private static final Comparator<Record> comparator = new Comparator<Record>() {
@Override @Override
public int compare(Record r1, Record r2) { public int compare(Record r1, Record r2) {
if (r1 instanceof LandmarkRecord){ return r1.order() - r2.order();
if (r2 instanceof LandmarkRecord){
return ((LandmarkRecord) r1).landmark.ordinal() - ((LandmarkRecord) r2).landmark.ordinal();
} else {
return -1;
}
} else if (r2 instanceof LandmarkRecord){
return 1;
} else {
//matches order in key display
return Generator.Category.order(((KeyRecord)r2).key) - Generator.Category.order(((KeyRecord)r1).key);
}
} }
}; };
public static void remove( Record rec ){
records.remove(rec);
}
} }
@@ -0,0 +1,160 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/>
*/
package com.shatteredpixel.shatteredpixeldungeon.ui;
import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Notes;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndJournalItem;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndTextInput;
import com.watabou.noosa.Game;
import com.watabou.utils.Callback;
//this is contained in its own class as custom notes have a lot of messy window UI logic
public class CustomNoteButton extends IconButton {
public CustomNoteButton () {
super(Icons.PLUS.get());
width = 11;
height = 11;
}
@Override
protected void onClick() {
super.onClick();
//TODO we want a selection window for note type here, atm it's only plaintext notes
Notes.CustomRecord custom = new Notes.CustomRecord(Messages.get(this, "default_title_text"), "");
Notes.add(custom);
refreshScene(custom);
}
@Override
protected String hoverText() {
return Messages.get(this, "hover_text");
}
public static class CustomNoteWindow extends WndJournalItem {
public CustomNoteWindow(Notes.CustomRecord rec) {
super(rec.icon(), rec.title(), rec.desc());
RedButton title = new RedButton( Messages.get(CustomNoteWindow.class, "edit_title") ){
@Override
protected void onClick() {
GameScene.show(new WndTextInput(Messages.get(CustomNoteWindow.class, "edit_title"),
"",
rec.title(),
50,
false,
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
public void onSelect(boolean positive, String text) {
if (positive){
rec.editText(text, rec.desc());
refreshScene(rec);
}
}
});
}
};
add(title);
title.setRect(0, Math.min(height+2, PixelScene.uiCamera.height-50), width/2-1, 16);
String editBodyText = rec.desc().isEmpty() ? Messages.get(CustomNoteWindow.class, "add_text") : Messages.get(CustomNoteWindow.class, "edit_text");
RedButton body = new RedButton(editBodyText){
@Override
protected void onClick() {
GameScene.show(new WndTextInput(editBodyText,
"",
rec.desc(),
500,
true,
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
public void onSelect(boolean positive, String text) {
if (positive){
rec.editText(rec.title(), text);
refreshScene(rec);
}
}
});
}
};
add(body);
body.setRect(title.right()+2, title.top(), width/2-1, 16);
RedButton delete = new RedButton( Messages.get(CustomNoteWindow.class, "delete") ){
@Override
protected void onClick() {
GameScene.show(new WndOptions(Icons.WARNING.get(),
Messages.get(CustomNoteWindow.class, "delete"),
Messages.get(CustomNoteWindow.class, "delete_warn"),
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
protected void onSelect(int index) {
if (index == 0){
Notes.remove(rec);
refreshScene(null);
}
}
});
}
};
add(delete);
delete.setRect(0, title.bottom()+1, width, 16);
resize(width, (int)delete.bottom());
}
}
private static void refreshScene(Notes.CustomRecord recToShow){
if (recToShow == null){
ShatteredPixelDungeon.seamlessResetScene();
} else {
ShatteredPixelDungeon.seamlessResetScene(new Game.SceneChangeCallback() {
@Override
public void beforeCreate() {
}
@Override
public void afterCreate() {
Game.runOnRenderThread(new Callback() {
@Override
public void call() {
ShatteredPixelDungeon.scene().addToFront(new CustomNoteWindow(recToShow));
}
});
}
});
}
}
}
@@ -53,6 +53,7 @@ import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.tiles.TerrainFeaturesTilemap; import com.shatteredpixel.shatteredpixeldungeon.tiles.TerrainFeaturesTilemap;
import com.shatteredpixel.shatteredpixeldungeon.ui.CustomNoteButton;
import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons;
import com.shatteredpixel.shatteredpixeldungeon.ui.QuickRecipe; import com.shatteredpixel.shatteredpixeldungeon.ui.QuickRecipe;
import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton;
@@ -424,6 +425,7 @@ public class WndJournal extends WndTabbed {
private static class NotesTab extends Component { private static class NotesTab extends Component {
private ScrollingGridPane grid; private ScrollingGridPane grid;
private CustomNoteButton custom;
@Override @Override
protected void createChildren() { protected void createChildren() {
@@ -443,6 +445,33 @@ public class WndJournal extends WndTabbed {
grid.addHeader(Messages.get(this, "desc"), 6, true); grid.addHeader(Messages.get(this, "desc"), 6, true);
ArrayList<Notes.CustomRecord> customRecs = Notes.getRecords(Notes.CustomRecord.class);
if (!customRecs.isEmpty()){
grid.addHeader("_" + Messages.get(this, "custom_notes") + "_");
for (Notes.CustomRecord rec : customRecs){
ScrollingGridPane.GridItem gridItem = new ScrollingGridPane.GridItem(rec.icon()){
@Override
public boolean onClick(float x, float y) {
if (inside(x, y)) {
GameScene.show(new CustomNoteButton.CustomNoteWindow(rec));
return true;
} else {
return false;
}
}
};
Visual secondIcon = rec.secondIcon();
if (secondIcon != null){
gridItem.addSecondIcon( secondIcon );
}
grid.addItem(gridItem);
}
}
for (int i = Statistics.deepestFloor; i > 0; i--){ for (int i = Statistics.deepestFloor; i > 0; i--){
ArrayList<Notes.Record> recs = Notes.getRecords(i); ArrayList<Notes.Record> recs = Notes.getRecords(i);
@@ -473,6 +502,10 @@ public class WndJournal extends WndTabbed {
} }
} }
custom = new CustomNoteButton();
grid.content().add(custom);
custom.setPos(width-custom.width()-1, 0);
grid.setRect(x, y, width, height); grid.setRect(x, y, width, height);
} }