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_COLLECTOR_GROUP_ID = 486;
public static final int BA_DEFENDER_GROUP_ID = 487; public static final int BA_DEFENDER_GROUP_ID = 487;
public static final int BA_HEALER_GROUP_ID = 488; 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 static class WorldMap
{ {
@@ -248,4 +250,14 @@ public class WidgetID
static final int ROLE_SPRITE = 6; static final int ROLE_SPRITE = 6;
static final int ROLE = 7; 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_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_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_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 groupId;
private final int childId; private final int childId;

View File

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

View File

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

View File

@@ -112,6 +112,8 @@ public class Hooks
{ {
log.warn("Error during overlay rendering", ex); 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) 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.BorderLayout;
import java.awt.Canvas; import java.awt.Canvas;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Frame; import java.awt.Frame;
import java.awt.Image;
import java.awt.SystemTray; import java.awt.SystemTray;
import java.awt.TrayIcon; import java.awt.TrayIcon;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration; import java.util.Enumeration;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -59,13 +68,16 @@ import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.client.RuneLiteProperties; import net.runelite.client.RuneLiteProperties;
import org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel; 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 @Slf4j
public class ClientUI extends JFrame public class ClientUI extends JFrame
{ {
private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH; private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH;
private static final BufferedImage ICON; private static final BufferedImage ICON;
private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
@Getter @Getter
private TrayIcon trayIcon; private TrayIcon trayIcon;
@@ -150,7 +162,36 @@ public class ClientUI extends JFrame
setLocationRelativeTo(getOwner()); setLocationRelativeTo(getOwner());
if (customChrome) 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); 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) private static void setUIFont(FontUIResource f)
{ {
final Enumeration keys = UIManager.getDefaults().keys(); 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.awt.image.BufferedImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.widgets.Widget; 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; import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
@Singleton @Singleton
@Slf4j
public class OverlayRenderer public class OverlayRenderer
{ {
private static final int BORDER_TOP = 25; private static final int BORDER_TOP = 25;
@@ -75,6 +79,8 @@ public class OverlayRenderer
private BufferedImage surface; private BufferedImage surface;
private Graphics2D surfaceGraphics; private Graphics2D surfaceGraphics;
private ConcurrentLinkedQueue<Consumer<BufferedImage>> screenshotRequests = new ConcurrentLinkedQueue<>();
@Subscribe @Subscribe
public void onResizableChanged(ResizeableChanged event) public void onResizableChanged(ResizeableChanged event)
{ {
@@ -299,4 +305,25 @@ public class OverlayRenderer
subGraphics.dispose(); subGraphics.dispose();
return dimension; 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