From 64dd3546f216ede8fe62e23ab5a90593116ca74b Mon Sep 17 00:00:00 2001 From: Ender Wiggin Date: Wed, 27 May 2015 23:27:40 +0300 Subject: [PATCH] added custom widgets for displaying text pages and editing them --- src/haven/RichTextBox.java | 21 ++- src/haven/TextPage.java | 302 ++++++++++++++++++++++++++++++++++ src/haven/TextPageEditor.java | 62 +++++++ 3 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 src/haven/TextPage.java create mode 100644 src/haven/TextPageEditor.java diff --git a/src/haven/RichTextBox.java b/src/haven/RichTextBox.java index a66bd17e..0fb183e8 100644 --- a/src/haven/RichTextBox.java +++ b/src/haven/RichTextBox.java @@ -34,12 +34,14 @@ public class RichTextBox extends Widget { private final RichText.Foundry fnd; private RichText text; private Scrollbar sb; + public boolean drawbox = true; public RichTextBox(Coord c, Coord sz, Widget parent, String text, RichText.Foundry fnd) { super(c, sz, parent); this.fnd = fnd; + this.sb = new Scrollbar(new Coord(sz.x - fbox.br.sz().x, fbox.bt.sz().y), sz.y - fbox.bt.sz().y - fbox.bb.sz().y, this, 0, 100); this.text = fnd.render(text, sz.x - 20); - this.sb = new Scrollbar(new Coord(sz.x - fbox.br.sz().x, fbox.bt.sz().y), sz.y - fbox.bt.sz().y - fbox.bb.sz().y, this, 0, this.text.sz().y + 20 - sz.y); + sb.max = this.text.sz().y + 20 - sz.y; } public RichTextBox(Coord c, Coord sz, Widget parent, String text, Object... attrs) { @@ -52,10 +54,23 @@ public void draw(GOut g) { g.frect(Coord.z, sz); g.chcolor(); } - g.image(text.tex(), new Coord(10, 10 - sb.val)); - fbox.draw(g, Coord.z, sz); + if(text != null){ + g.image(text.tex(), textshift()); + } + if(drawbox){ + fbox.draw(g, Coord.z, sz); + } super.draw(g); } + + public RichText.Part partat(Coord c){ + return text.partat(c.sub(textshift())); + } + + protected Coord textshift(){ + int v = 10 - (sb == null ? 0 : sb.val); + return new Coord(10, v); + } public void settext(String text) { this.text = fnd.render(text, sz.x - 20); diff --git a/src/haven/TextPage.java b/src/haven/TextPage.java new file mode 100644 index 00000000..1a664986 --- /dev/null +++ b/src/haven/TextPage.java @@ -0,0 +1,302 @@ +package haven; + +import haven.ChatUI.ChatAttribute; +import haven.ChatUI.FuckMeGentlyWithAChainsaw; + +import java.awt.*; +import java.awt.font.TextAttribute; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.AttributedCharacterIterator; +import java.text.AttributedCharacterIterator.Attribute; +import java.util.HashMap; +import java.util.Map; + +public class TextPage extends RichTextBox { + private static final RichText.Foundry fnd; + public static final String WIKI_BASE_URL = "http://www.salem-wiki.com/mediawiki/index.php?title="; + private final Tex paper = Resource.loadtex("gfx/hud/blankpaper"); + private RichText.Part ttpart = null; + private Tex tt = null; + + static { + Font font = null; + try { + font = Font.createFont(Font.TRUETYPE_FONT, Config.getFile("mordred.regular.ttf")); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + ge.registerFont(font.deriveFont(Font.PLAIN, 18)); + } catch (FontFormatException ignored) { + } catch (IOException ignored) { + } + String family = font != null ? font.getFamily() : "Serif"; + fnd = new RichText.Foundry(new Parser( + TextAttribute.FAMILY, family, + TextAttribute.SIZE, 18, + TextAttribute.FOREGROUND, Color.BLACK + )); + fnd.aa = true; + } + + @RName("textpage") + public static class $_ implements Factory { + public Widget create(Coord c, Widget parent, Object[] args) { + return new TextPage(c, (Coord) args[0], parent, (String) args[1]); + } + } + + private static class Parser extends RichText.Parser { + @Override + protected RichText.Part tag(PState s, String tn, String[] args, Map attrs) throws IOException { + if(tn.equals("img")) { + int id = -1; + if(args.length > 1) + id = Integer.parseInt(args[1]); + return (new Image(args[0], id)); + } else if(tn.equals("item")) { + Image img = new Image("gfx/invobjs/" + args[0], -1); + String name = img.res.layer(Resource.tooltip).t; + try { + img.url = new URL(WIKI_BASE_URL + URLEncoder.encode(name, "UTF-8")); + } catch (java.net.MalformedURLException ignored) {} + return img; + } else if(tn.equals("menu")) { + return new Image("paginae/" + args[0], -1); + } else { + Map na = new HashMap(attrs); + boolean found = false; + if(tn.equals("c")) { + na.put(TextAttribute.FOREGROUND, new Color(Integer.parseInt(args[0], 16))); + found = true; + } else if(tn.equals("h1")) { + float sz = a2float(attrs.get(TextAttribute.SIZE)); + if(sz > 0) { + na.put(TextAttribute.SIZE, Math.round(1.6 * sz)); + na.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); + found = true; + } + } else if(tn.equals("h2")) { + float sz = a2float(attrs.get(TextAttribute.SIZE)); + if(sz > 0) { + na.put(TextAttribute.SIZE, Math.round(1.2 * sz)); + na.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); + found = true; + } + } else if(tn.equals("url")) { + try { + if(args[0].indexOf(':') < 0) + args[0] = "http://" + args[0]; + URL url = new URL(args[0]); + na.putAll(ChatUI.ChatParser.urlstyle); + na.put(ChatAttribute.HYPERLINK, new FuckMeGentlyWithAChainsaw(url)); + found = true; + }catch(Exception ignored){ + ignored.printStackTrace(); + } + } + if(found) { + if(s.in.peek(true) != '{') + throw (new RichText.FormatException("Expected `{', got `" + (char) s.in.peek() + "'")); + s.in.read(); + return (text(s, na)); + } else { + return super.tag(s, tn, args, attrs); + } + } + } + + public Parser(Object... attrs) { + super(attrs); + } + + protected float a2float(Object val) { + float res = 0; + if(val instanceof String) { + try { + res = Float.parseFloat((String) val); + } catch (Exception ignored) { + } + } else { + try { + res = (Float) val; + } catch (Exception ignored) { + } + } + return res; + } + } + + public static class Image extends RichText.Part { + public BufferedImage img; + public Resource res; + public URL url; + + //public Image(Resource res, int id) { + public Image(String name, int id) { + try { + res = Resource.load(name); + res.loadwait(); + for (Resource.Image img : res.layers(Resource.imgc)) { + if(img.id == id) { + this.img = img.img; + break; + } + } + } catch (RuntimeException error) { + this.img = Resource.load("gfx/invobjs/missing").layer(Resource.imgc).img; + } + if(this.img == null) + throw (new RuntimeException("Found no image with id " + id + " in " + name)); + } + + public int width() { + return (img.getWidth()); + } + + public int height() { + return (img.getHeight()); + } + + public int baseline() { + return (img.getHeight() - 1); + } + + public void render(Graphics2D g) { + g.drawImage(img, x, y, null); + } + } + + public TextPage(Coord c, Coord sz, Widget parent, String text) { + super(c, sz, parent, "", fnd); + bg = null; + drawbox = false; + settext(text); + } + + @Override + public void settext(String text) { + text = text.replaceAll("/n", "\n"); + text = text.replaceAll("\\$t\\{", "\\$size[30]{"); + try { + super.settext(text); + } catch (Exception error) { + super.settext(RichText.Parser.quote(text)); + } + } + + private URL geturl(Coord c) { + RichText.Part part = partat(c); + if(part instanceof Image){ + return ((Image)part).url; + } else if(part instanceof RichText.TextPart){ + RichText.TextPart textPart = (RichText.TextPart) part; + int index = textPart.charat(c.sub(textshift())).getCharIndex() + textPart.start; + AttributedCharacterIterator inf = textPart.ti(); + try { + inf.setIndex(index); + }catch(Exception e){ + e.printStackTrace(); + } + FuckMeGentlyWithAChainsaw url = (FuckMeGentlyWithAChainsaw)inf.getAttribute(ChatAttribute.HYPERLINK); + if(url != null){ + return url.url; + } + } + return null; + } + + private Resource getaction(Coord c){ + RichText.Part part = partat(c); + if(part instanceof Image){ + Resource res = ((Image) part).res; + if(res != null && res.layer(Resource.action) != null){ + return res; + } + } + return null; + } + + @Override + public boolean mousedown(Coord c, int button) { + return geturl(c) != null || getaction(c) != null || super.mousedown(c, button); + } + + @Override + public boolean mouseup(Coord c, int button) { + URL url = geturl(c); + Resource action = getaction(c); + if(url != null && WebBrowser.self != null) { + try { + WebBrowser.self.show(url); + } catch(WebBrowser.BrowserException e) { + getparent(GameUI.class).error("Could not launch web browser."); + } + return true; + } else if(action != null) { + ui.gui.menu.useres(action); + return true; + } + return super.mouseup(c, button); + } + + @Override + public Object tooltip(Coord c, Widget prev) { + RichText.Part p = partat(c); + URL url = geturl(c); + if(p == ttpart && tt != null) { + return tt; + } else { + tt = null; + if(p instanceof Image) { + Image img = (Image) p; + Text text = null; + Resource.Tooltip tip = img.res.layer(Resource.tooltip); + if(tip != null) { + text = Text.render(tip.t); + } else { + Resource.AButton action = img.res.layer(Resource.action); + if(action != null) { + text = Text.render(action.name); + } + } + Text urltex = null; + if(url != null){ + urltex = Text.render(url.toString(), Color.LIGHT_GRAY); + } + if(text != null || urltex != null){ + ttpart = p; + if(text != null && urltex != null){ + tt = new TexI(ItemInfo.catimgs(2, text.img, urltex.img)); + } else if(text != null){ + tt = text.tex(); + } else { + tt = urltex.tex(); + } + } + } else if(p instanceof RichText.TextPart && url != null){ + ttpart = p; + tt = Text.render(url.toString()).tex(); + } + if(tt != null){ + return tt; + } + } + return super.tooltip(c, prev); + } + + @Override + public void draw(GOut g) { + g.image(paper, Coord.z); + super.draw(g); + } + + @Override + public void uimsg(String msg, Object... args) { + if(msg.equals("set")) { + settext((String) args[0]); + } else { + super.uimsg(msg, args); + } + } +} diff --git a/src/haven/TextPageEditor.java b/src/haven/TextPageEditor.java new file mode 100644 index 00000000..c5d2f049 --- /dev/null +++ b/src/haven/TextPageEditor.java @@ -0,0 +1,62 @@ +package haven; + +import java.awt.*; +import java.awt.event.KeyEvent; + +public class TextPageEditor extends Widget implements DTarget { + private static final int MAX_CHARS = 1000; + TextPage page; + TextEntry text; + Label symbols; + + @RName("textpageedit") + public static class $_ implements Factory { + public Widget create(Coord c, Widget parent, Object[] args) { + return new TextPageEditor(c, parent); + } + } + + public TextPageEditor(Coord c, Widget parent) { + super(c, new Coord(485, 550), parent); + + page = new TextPage(Coord.z, new Coord(485, 500), this, ""); + text = new TextEntry(new Coord(0, 505), 485, this, "") { + @Override + public void activate(String text) { + if(!ui.modctrl) { + buf.key('/', KeyEvent.VK_SLASH, 0); + buf.key('n', KeyEvent.VK_N, 0); + } else { + TextPageEditor.this.wdgmsg("activate", text); + } + } + + @Override + protected void changed() { + textchanged(text); + } + }; + symbols = new Label(new Coord(0, 530), this, ""); + textchanged(""); + } + + private void textchanged(String text) { + page.settext(text); + int length = text.length(); + boolean ok = length <= MAX_CHARS; + symbols.col = ok ? Color.WHITE : Color.RED; + symbols.settext(String.format("Symbols left: %d/%d Press CTRL+Enter to submit.", MAX_CHARS - length, MAX_CHARS)); + } + + @Override + public boolean drop(Coord cc, Coord ul) { + parent.mousedown(parent.xlate(cc, true), 1); + return true; + } + + @Override + public boolean iteminteract(Coord cc, Coord ul) { + parent.mousedown(parent.xlate(cc, true), 3); + return true; + } +}