Merge pull request #395 from UniquePassive/screenshot-plugin

Screenshot plugin
This commit is contained in:
Adam
2018-01-20 11:32:53 -05:00
committed by GitHub
16 changed files with 696 additions and 130 deletions

View File

@@ -54,6 +54,8 @@ public class WidgetID
public static final int BA_COLLECTOR_GROUP_ID = 486;
public static final int BA_DEFENDER_GROUP_ID = 487;
public static final int BA_HEALER_GROUP_ID = 488;
public static final int LEVEL_UP_GROUP_ID = 233;
public static final int QUEST_COMPLETED_GROUP_ID = 277;
static class WorldMap
{
@@ -248,4 +250,14 @@ public class WidgetID
static final int ROLE_SPRITE = 6;
static final int ROLE = 7;
}
static class LevelUp
{
static final int TEXT = 1;
}
static class QuestCompleted
{
static final int NAME_TEXT = 2;
}
}

View File

@@ -152,7 +152,13 @@ public enum WidgetInfo
BA_DEF_CALL_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_DEF_LISTEN_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.CORRECT_STYLE),
BA_DEF_ROLE_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE);
BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
LEVEL_UP(WidgetID.LEVEL_UP_GROUP_ID, 0),
LEVEL_UP_TEXT(WidgetID.LEVEL_UP_GROUP_ID, WidgetID.LevelUp.TEXT),
QUEST_COMPLETED(WidgetID.QUEST_COMPLETED_GROUP_ID, 0),
QUEST_COMPLETED_NAME_TEXT(WidgetID.QUEST_COMPLETED_GROUP_ID, WidgetID.QuestCompleted.NAME_TEXT);
private final int groupId;
private final int childId;

View File

@@ -78,7 +78,7 @@
<dependency>
<groupId>org.pushingpixels</groupId>
<artifactId>substance</artifactId>
<version>7.1.00-rc</version>
<version>8.0.00-dev</version>
</dependency>
<dependency>
<groupId>org.pushingpixels</groupId>

View File

@@ -39,6 +39,7 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.client.events.ClientUILoaded;
import net.runelite.client.account.SessionManager;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.config.ConfigManager;
@@ -55,6 +56,7 @@ public class RuneLite
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
private static Injector injector;
private static OptionSet options;
@@ -164,6 +166,8 @@ public class RuneLite
pluginManager.watch();
SwingUtilities.invokeAndWait(() -> gui.showWithChrome(runeliteConfig.enableCustomChrome()));
eventBus.post(new ClientUILoaded());
}
public void setGui(ClientUI gui)

View File

@@ -112,6 +112,8 @@ public class Hooks
{
log.warn("Error during overlay rendering", ex);
}
renderer.provideScreenshot(image);
}
public static void drawRegion(Region region, int var1, int var2, int var3, int var4, int var5, int var6)

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.events;
import lombok.Data;
@Data
public class ClientUILoaded
{
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup(
keyName = "screenshot",
name = "Screenshot",
description = "Configuration for the Screenshot plugin"
)
public interface ScreenshotConfig extends Config
{
@ConfigItem(
keyName = "includeFrame",
name = "Include Client Frame",
description = "Configures whether or not the client frame is included in screenshots",
position = 0
)
default boolean includeFrame()
{
return true;
}
@ConfigItem(
keyName = "displayDate",
name = "Display Date",
description = "Configures whether or not the report button shows the date the screenshot was taken",
position = 1
)
default boolean displayDate()
{
return true;
}
@ConfigItem(
keyName = "notifyWhenTaken",
name = "Notify When Taken",
description = "Configures whether or not you are notified when a screenshot has been taken",
position = 2
)
default boolean notifyWhenTaken()
{
return true;
}
@ConfigItem(
keyName = "uploadScreenshot",
name = "Upload To Imgur",
description = "Configures whether or not screenshots are uploaded to Imgur and copied into your clipboard",
position = 3
)
default boolean uploadScreenshot()
{
return false;
}
}

View File

@@ -0,0 +1,365 @@
/*
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot;
import net.runelite.client.plugins.screenshot.imgur.ImageUploadRequest;
import net.runelite.client.plugins.screenshot.imgur.ImageUploadResponse;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.InputStreamReader;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import net.runelite.api.Client;
import net.runelite.client.events.ClientUILoaded;
import net.runelite.api.events.WidgetHiddenChanged;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.Notifier;
import net.runelite.client.RuneLite;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.OverlayRenderer;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import static net.runelite.api.widgets.WidgetID.LEVEL_UP_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.QUEST_COMPLETED_GROUP_ID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import okhttp3.HttpUrl;
@PluginDescriptor(
name = "Screenshot plugin"
)
@Slf4j
public class ScreenshotPlugin extends Plugin
{
private static final String IMGUR_CLIENT_ID = "30d71e5f6860809";
private static final HttpUrl IMGUR_IMAGE_UPLOAD_URL = HttpUrl.parse("https://api.imgur.com/3/image");
private static final MediaType JSON = MediaType.parse("application/json");
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM. dd, yyyy", Locale.US);
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("h.m.s a d MMM. yyyy", Locale.US);
@Inject
ScreenshotConfig config;
@Inject
Notifier notifier;
@Inject
Client client;
@Inject
ClientUI clientUi;
@Inject
OverlayRenderer overlayRenderer;
private JButton titleBarButton;
@Provides
ScreenshotConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(ScreenshotConfig.class);
}
@Override
protected void startUp() throws Exception
{
// prior to UI loading this does nothing
addButtonToTitleBar();
}
@Override
protected void shutDown() throws Exception
{
removeButtonFromTitlebar();
}
@Subscribe
public void clientUiLoaded(ClientUILoaded e)
{
addButtonToTitleBar();
}
private void addButtonToTitleBar()
{
try
{
BufferedImage iconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot.png"));
BufferedImage invertedIconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot_inverted.png"));
SwingUtilities.invokeLater(() ->
{
titleBarButton = new JButton();
titleBarButton.setToolTipText("Take screenshot");
titleBarButton.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
super.mouseClicked(e);
takeScreenshot(TIME_FORMAT.format(new Date()), client.getLocalPlayer() != null);
}
});
clientUi.addButtonToTitleBar(titleBarButton, iconImage, invertedIconImage, 130);
});
}
catch (IOException ex)
{
log.warn("Error adding screenshot button to titlebar", ex);
}
}
private void removeButtonFromTitlebar()
{
SwingUtilities.invokeLater(() ->
{
JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(clientUi);
titleBar.remove(titleBarButton);
clientUi.revalidate();
clientUi.repaint();
});
}
@Subscribe
public void hideWidgets(WidgetHiddenChanged event)
{
Widget widget = event.getWidget();
if (widget.isHidden())
{
return;
}
String fileName;
switch (TO_GROUP(widget.getId()))
{
case LEVEL_UP_GROUP_ID:
{
Widget textChild = client.getWidget(WidgetInfo.LEVEL_UP_TEXT);
if (textChild == null)
{
return;
}
// "Your Firemaking level is now 9."
String text = textChild.getText();
String skillName = text.substring(5, text.indexOf(" level"));
String skillLevel = text.substring(text.lastIndexOf(" ") + 1, text.length() - 1);
fileName = skillName + " (" + skillLevel + ")";
break;
}
case QUEST_COMPLETED_GROUP_ID:
{
Widget textChild = client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT);
if (textChild == null)
{
return;
}
// "You have completed The Corsair Curse!"
String text = textChild.getText();
fileName = text.substring(19, text.length() - 1);
break;
}
default:
return;
}
takeScreenshot(fileName, config.displayDate());
}
private void takeScreenshot(String fileName, boolean displayDate)
{
overlayRenderer.requestScreenshot(image ->
{
BufferedImage screenshot = config.includeFrame()
? new BufferedImage(clientUi.getWidth(), clientUi.getHeight(), BufferedImage.TYPE_INT_ARGB)
: new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphics = screenshot.getGraphics();
int gameOffsetX = 0;
int gameOffsetY = 0;
if (config.includeFrame())
{
// Draw the client frame onto the screenshot
clientUi.paint(graphics);
// Evaluate the position of the game inside the frame
Point gamePoint = SwingUtilities.convertPoint(client.getCanvas(), 0, 0, clientUi);
gameOffsetX = gamePoint.x;
gameOffsetY = gamePoint.y;
}
// Draw the game onto the screenshot
graphics.drawImage(image, gameOffsetX, gameOffsetY, null);
if (displayDate)
{
try (InputStream reportButton = ScreenshotPlugin.class.getResourceAsStream("report_button.png"))
{
BufferedImage reportButtonImage = ImageIO.read(reportButton);
int x = gameOffsetX + 403;
int y = gameOffsetY + image.getHeight() - reportButtonImage.getHeight() - 1;
graphics.drawImage(reportButtonImage, x, y, null);
graphics.setFont(FontManager.getRunescapeSmallFont());
FontMetrics fontMetrics = graphics.getFontMetrics();
String date = DATE_FORMAT.format(new Date());
int dateWidth = fontMetrics.stringWidth(date);
int dateHeight = fontMetrics.getHeight();
int textX = x + reportButtonImage.getWidth() / 2 - dateWidth / 2;
int textY = y + reportButtonImage.getHeight() / 2 + dateHeight / 2;
graphics.setColor(Color.BLACK);
graphics.drawString(date, textX + 1, textY + 1);
graphics.setColor(Color.WHITE);
graphics.drawString(date, textX, textY);
}
catch (Exception ex)
{
log.warn("error displaying date on screenshot", ex);
}
}
File playerFolder = RuneLite.SCREENSHOT_DIR;
if (client.getLocalPlayer() != null)
{
playerFolder = new File(RuneLite.SCREENSHOT_DIR, client.getLocalPlayer().getName());
}
playerFolder.mkdirs();
try
{
File screenshotFile = new File(playerFolder, fileName + ".png");
ImageIO.write(screenshot, "PNG", screenshotFile);
if (config.uploadScreenshot())
{
uploadScreenshot(screenshotFile);
}
else if (config.notifyWhenTaken())
{
notifier.notify("A screenshot was saved to " + screenshotFile, TrayIcon.MessageType.INFO);
}
}
catch (IOException ex)
{
log.warn("error writing screenshot", ex);
}
});
}
private void uploadScreenshot(File screenshotFile) throws IOException
{
String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile));
Request request = new Request.Builder()
.url(IMGUR_IMAGE_UPLOAD_URL)
.addHeader("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.post(RequestBody.create(JSON, json))
.build();
RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException ex)
{
log.warn("error uploading screenshot", ex);
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
InputStream in = response.body().byteStream();
ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON
.fromJson(new InputStreamReader(in), ImageUploadResponse.class);
if (imageUploadResponse.isSuccess())
{
String link = imageUploadResponse.getData().getLink();
StringSelection selection = new StringSelection(link);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(selection, selection);
if (config.notifyWhenTaken())
{
notifier.notify("A screenshot was uploaded and inserted into your clipboard!", TrayIcon.MessageType.INFO);
}
}
}
});
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot.imgur;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Base64;
import lombok.Data;
@Data
public class ImageUploadRequest
{
private final String image;
private final String type;
public ImageUploadRequest(File imageFile) throws IOException
{
this.image = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
this.type = "base64";
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot.imgur;
import lombok.Data;
@Data
public class ImageUploadResponse
{
private Data data;
private boolean success;
@lombok.Data
public static class Data
{
private String link;
}
}

View File

@@ -30,19 +30,28 @@ import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
@@ -59,13 +68,16 @@ import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.client.RuneLiteProperties;
import org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel;
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
import org.pushingpixels.substance.internal.SubstanceSynapse;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities;
@Slf4j
public class ClientUI extends JFrame
{
private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH;
private static final BufferedImage ICON;
private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
@Getter
private TrayIcon trayIcon;
@@ -150,7 +162,36 @@ public class ClientUI extends JFrame
setLocationRelativeTo(getOwner());
if (customChrome)
{
new TitleBarPane(this.getRootPane(), (SubstanceRootPaneUI) this.getRootPane().getUI()).editTitleBar(this);
try
{
BufferedImage discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
BufferedImage invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
JButton discordButton = new JButton();
discordButton.setToolTipText("Join Discord");
discordButton.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
super.mouseClicked(e);
try
{
Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
}
catch (IOException | URISyntaxException ex)
{
log.warn("error opening browser", ex);
}
}
});
addButtonToTitleBar(discordButton, discordIcon, invertedIcon, 100);
}
catch (IOException ex)
{
log.warn("unable to load discord button", ex);
}
}
setVisible(true);
@@ -172,6 +213,44 @@ public class ClientUI extends JFrame
}
}
public void addButtonToTitleBar(JButton button, Image iconImage, Image invertedIconImage, int xOffset)
{
JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(this);
if (titleBar == null)
{
return;
}
int size = titleBar.getHeight() - 6;
ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
ImageIcon invertedIcon = new ImageIcon(invertedIconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
button.setIcon(icon);
button.setRolloverIcon(invertedIcon);
button.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE);
button.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING);
button.setFocusable(false);
button.setBounds(titleBar.getWidth() - xOffset, 2,
icon.getIconWidth() + 4, icon.getIconHeight() + 2);
titleBar.addComponentListener(new ComponentAdapter()
{
@Override
public void componentResized(ComponentEvent e)
{
super.componentResized(e);
button.setBounds(titleBar.getWidth() - xOffset, 1, button.getWidth(), button.getHeight());
}
});
titleBar.add(button);
revalidate();
repaint();
}
private static void setUIFont(FontUIResource f)
{
final Enumeration keys = UIManager.getDefaults().keys();

View File

@@ -1,126 +0,0 @@
/*
* Copyright (c) 2017. l2-
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui;
import java.awt.Desktop;
import java.awt.Image;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import lombok.extern.slf4j.Slf4j;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
import org.pushingpixels.substance.internal.utils.SubstanceTitlePane;
@Slf4j
public class TitleBarPane extends SubstanceTitlePane
{
private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
private BufferedImage discordIcon;
private BufferedImage invertedIcon;
public TitleBarPane(JRootPane root, SubstanceRootPaneUI ui)
{
super(root, ui);
try
{
discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
}
catch (IOException ex)
{
log.warn("unable to load discord icons", ex);
}
}
public void editTitleBar(JFrame frame)
{
if (discordIcon == null || invertedIcon == null)
{
return;
}
JComponent titleBar = SubstanceLookAndFeel.getTitlePaneComponent(frame);
JButton discordButton = new JButton();
int heigth = titleBar.getHeight() - 3;
int width = heigth;
int x = titleBar.getWidth() - 100;
int y = 1;
ImageIcon icon = new ImageIcon(discordIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
ImageIcon invIcon = new ImageIcon(invertedIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
discordButton.setIcon(icon);
discordButton.setRolloverIcon(invIcon);
discordButton.putClientProperty("substancelaf.componentFlat", Boolean.TRUE);
discordButton.putClientProperty("substancelaf.internal.titlePane.extraComponentKind", ExtraComponentKind.TRAILING);
discordButton.setFocusable(false);
discordButton.setBounds(x, y, icon.getIconWidth() + 4, icon.getIconHeight() + 2);
discordButton.setToolTipText("Join Discord");
discordButton.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
super.mouseClicked(e);
try
{
Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
}
catch (IOException | URISyntaxException ex)
{
log.warn("error opening browser", ex);
}
}
});
titleBar.addComponentListener(new ComponentAdapter()
{
@Override
public void componentResized(ComponentEvent e)
{
super.componentResized(e);
discordButton.setBounds(titleBar.getWidth() - 100, y, discordButton.getWidth(), discordButton.getHeight());
}
});
titleBar.add(discordButton);
}
}

View File

@@ -34,11 +34,14 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.widgets.Widget;
@@ -51,6 +54,7 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
@Singleton
@Slf4j
public class OverlayRenderer
{
private static final int BORDER_TOP = 25;
@@ -75,6 +79,8 @@ public class OverlayRenderer
private BufferedImage surface;
private Graphics2D surfaceGraphics;
private ConcurrentLinkedQueue<Consumer<BufferedImage>> screenshotRequests = new ConcurrentLinkedQueue<>();
@Subscribe
public void onResizableChanged(ResizeableChanged event)
{
@@ -299,4 +305,25 @@ public class OverlayRenderer
subGraphics.dispose();
return dimension;
}
public void provideScreenshot(BufferedImage image)
{
Consumer<BufferedImage> consumer;
while ((consumer = screenshotRequests.poll()) != null)
{
try
{
consumer.accept(image);
}
catch (Exception ex)
{
log.warn("error in screenshot callback", ex);
}
}
}
public void requestScreenshot(Consumer<BufferedImage> consumer)
{
screenshotRequests.add(consumer);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B