config: add support for sections
This commit is contained in:
@@ -24,26 +24,20 @@
|
||||
*/
|
||||
package net.runelite.client.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import java.util.Collection;
|
||||
|
||||
@Getter
|
||||
public class ConfigDescriptor
|
||||
{
|
||||
private final ConfigGroup group;
|
||||
private final Collection<ConfigSectionDescriptor> sections;
|
||||
private final Collection<ConfigItemDescriptor> items;
|
||||
|
||||
public ConfigDescriptor(ConfigGroup group, Collection<ConfigItemDescriptor> items)
|
||||
public ConfigDescriptor(ConfigGroup group, Collection<ConfigSectionDescriptor> sections, Collection<ConfigItemDescriptor> items)
|
||||
{
|
||||
this.group = group;
|
||||
this.sections = sections;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public ConfigGroup getGroup()
|
||||
{
|
||||
return group;
|
||||
}
|
||||
|
||||
public Collection<ConfigItemDescriptor> getItems()
|
||||
{
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,6 @@ public @interface ConfigItem
|
||||
String warning() default "";
|
||||
|
||||
boolean secret() default false;
|
||||
|
||||
String section() default "";
|
||||
}
|
||||
|
||||
@@ -27,11 +27,29 @@ package net.runelite.client.config;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class ConfigItemDescriptor
|
||||
public class ConfigItemDescriptor implements ConfigObject
|
||||
{
|
||||
private final ConfigItem item;
|
||||
private final Class<?> type;
|
||||
private final Range range;
|
||||
private final Alpha alpha;
|
||||
private final Units units;
|
||||
|
||||
@Override
|
||||
public String key()
|
||||
{
|
||||
return item.keyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
return item.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int position()
|
||||
{
|
||||
return item.position();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,8 +468,32 @@ public class ConfigManager
|
||||
throw new IllegalArgumentException("Not a config group");
|
||||
}
|
||||
|
||||
final List<ConfigSectionDescriptor> sections = Arrays.stream(inter.getDeclaredFields())
|
||||
.filter(m -> m.isAnnotationPresent(ConfigSection.class) && m.getType() == String.class)
|
||||
.map(m ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ConfigSectionDescriptor(
|
||||
String.valueOf(m.get(inter)),
|
||||
m.getDeclaredAnnotation(ConfigSection.class)
|
||||
);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
log.warn("Unable to load section {}::{}", inter.getSimpleName(), m.getName());
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted((a, b) -> ComparisonChain.start()
|
||||
.compare(a.getSection().position(), b.getSection().position())
|
||||
.compare(a.getSection().name(), b.getSection().name())
|
||||
.result())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<ConfigItemDescriptor> items = Arrays.stream(inter.getMethods())
|
||||
.filter(m -> m.getParameterCount() == 0)
|
||||
.filter(m -> m.getParameterCount() == 0 && m.isAnnotationPresent(ConfigItem.class))
|
||||
.map(m -> new ConfigItemDescriptor(
|
||||
m.getDeclaredAnnotation(ConfigItem.class),
|
||||
m.getReturnType(),
|
||||
@@ -483,7 +507,7 @@ public class ConfigManager
|
||||
.result())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new ConfigDescriptor(group, items);
|
||||
return new ConfigDescriptor(group, sections, items);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Hydrox6 <ikada@protonmail.ch>
|
||||
* 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.config;
|
||||
|
||||
public interface ConfigObject
|
||||
{
|
||||
String key();
|
||||
String name();
|
||||
int position();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Hydrox6 <ikada@protonmail.ch>
|
||||
* 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.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ConfigSection
|
||||
{
|
||||
String name();
|
||||
|
||||
String description();
|
||||
|
||||
int position();
|
||||
|
||||
boolean closedByDefault() default false;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Hydrox6 <ikada@protonmail.ch>
|
||||
* 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.config;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class ConfigSectionDescriptor implements ConfigObject
|
||||
{
|
||||
private final String key;
|
||||
private final ConfigSection section;
|
||||
|
||||
@Override
|
||||
public String key()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
return section.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int position()
|
||||
{
|
||||
return section.position();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
package net.runelite.client.plugins.config;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
@@ -36,8 +37,12 @@ import java.awt.event.ItemEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import javax.inject.Inject;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
@@ -55,7 +60,9 @@ import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.MatteBorder;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -63,6 +70,9 @@ import net.runelite.client.config.ConfigDescriptor;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
import net.runelite.client.config.ConfigItemDescriptor;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.ConfigObject;
|
||||
import net.runelite.client.config.ConfigSection;
|
||||
import net.runelite.client.config.ConfigSectionDescriptor;
|
||||
import net.runelite.client.config.Keybind;
|
||||
import net.runelite.client.config.ModifierlessKeybind;
|
||||
import net.runelite.client.config.Range;
|
||||
@@ -76,6 +86,7 @@ import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginManager;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.DynamicGridLayout;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.ui.components.ComboBoxListRenderer;
|
||||
import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
|
||||
@@ -89,9 +100,15 @@ import net.runelite.client.util.Text;
|
||||
class ConfigPanel extends PluginPanel
|
||||
{
|
||||
private static final int SPINNER_FIELD_WIDTH = 6;
|
||||
private static final ImageIcon SECTION_EXPAND_ICON;
|
||||
private static final ImageIcon SECTION_EXPAND_ICON_HOVER;
|
||||
private static final ImageIcon SECTION_RETRACT_ICON;
|
||||
private static final ImageIcon SECTION_RETRACT_ICON_HOVER;
|
||||
static final ImageIcon BACK_ICON;
|
||||
static final ImageIcon BACK_ICON_HOVER;
|
||||
|
||||
private static final Map<ConfigSectionDescriptor, Boolean> sectionExpandStates = new HashMap<>();
|
||||
|
||||
private final FixedWidthPanel mainPanel;
|
||||
private final JLabel title;
|
||||
private final PluginToggleButton pluginToggle;
|
||||
@@ -118,6 +135,14 @@ class ConfigPanel extends PluginPanel
|
||||
final BufferedImage backIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "config_back_icon.png");
|
||||
BACK_ICON = new ImageIcon(backIcon);
|
||||
BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -100));
|
||||
|
||||
BufferedImage sectionRetractIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "/util/arrow_right.png");
|
||||
sectionRetractIcon = ImageUtil.luminanceOffset(sectionRetractIcon, -121);
|
||||
SECTION_EXPAND_ICON = new ImageIcon(sectionRetractIcon);
|
||||
SECTION_EXPAND_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionRetractIcon, -100));
|
||||
final BufferedImage sectionExpandIcon = ImageUtil.rotateImage(sectionRetractIcon, Math.PI / 2);
|
||||
SECTION_RETRACT_ICON = new ImageIcon(sectionExpandIcon);
|
||||
SECTION_RETRACT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionExpandIcon, -100));
|
||||
}
|
||||
|
||||
public ConfigPanel()
|
||||
@@ -205,11 +230,91 @@ class ConfigPanel extends PluginPanel
|
||||
rebuild();
|
||||
}
|
||||
|
||||
private void toggleSection(ConfigSectionDescriptor csd, JButton button, JPanel contents)
|
||||
{
|
||||
boolean newState = !contents.isVisible();
|
||||
contents.setVisible(newState);
|
||||
button.setIcon(newState ? SECTION_RETRACT_ICON : SECTION_EXPAND_ICON);
|
||||
button.setRolloverIcon(newState ? SECTION_RETRACT_ICON_HOVER : SECTION_EXPAND_ICON_HOVER);
|
||||
button.setToolTipText(newState ? "Retract" : "Expand");
|
||||
sectionExpandStates.put(csd, newState);
|
||||
SwingUtilities.invokeLater(contents::revalidate);
|
||||
}
|
||||
|
||||
private void rebuild()
|
||||
{
|
||||
mainPanel.removeAll();
|
||||
|
||||
ConfigDescriptor cd = pluginConfig.getConfigDescriptor();
|
||||
|
||||
final Map<String, JPanel> sectionWidgets = new HashMap<>();
|
||||
final Map<ConfigObject, JPanel> topLevelPanels = new TreeMap<>((a, b) ->
|
||||
ComparisonChain.start()
|
||||
.compare(a.position(), b.position())
|
||||
.compare(a.name(), b.name())
|
||||
.result());
|
||||
|
||||
for (ConfigSectionDescriptor csd : cd.getSections())
|
||||
{
|
||||
ConfigSection cs = csd.getSection();
|
||||
final boolean isOpen = sectionExpandStates.getOrDefault(csd, !cs.closedByDefault());
|
||||
|
||||
final JPanel section = new JPanel();
|
||||
section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
|
||||
section.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
|
||||
|
||||
final JPanel sectionHeader = new JPanel();
|
||||
sectionHeader.setLayout(new BorderLayout());
|
||||
sectionHeader.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
|
||||
// For whatever reason, the header extends out by a single pixel when closed. Adding a single pixel of
|
||||
// border on the right only affects the width when closed, fixing the issue.
|
||||
sectionHeader.setBorder(new CompoundBorder(
|
||||
new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR),
|
||||
new EmptyBorder(0, 0, 3, 1)));
|
||||
section.add(sectionHeader, BorderLayout.NORTH);
|
||||
|
||||
final JButton sectionToggle = new JButton(isOpen ? SECTION_RETRACT_ICON : SECTION_EXPAND_ICON);
|
||||
sectionToggle.setRolloverIcon(isOpen ? SECTION_RETRACT_ICON_HOVER : SECTION_EXPAND_ICON_HOVER);
|
||||
sectionToggle.setPreferredSize(new Dimension(18, 0));
|
||||
sectionToggle.setBorder(new EmptyBorder(0, 0, 0, 5));
|
||||
sectionToggle.setToolTipText(isOpen ? "Retract" : "Expand");
|
||||
SwingUtil.removeButtonDecorations(sectionToggle);
|
||||
sectionHeader.add(sectionToggle, BorderLayout.WEST);
|
||||
|
||||
String name = cs.name();
|
||||
final JLabel sectionName = new JLabel(name);
|
||||
sectionName.setForeground(ColorScheme.BRAND_ORANGE);
|
||||
sectionName.setFont(FontManager.getRunescapeBoldFont());
|
||||
sectionName.setToolTipText("<html>" + name + ":<br>" + cs.description() + "</html>");
|
||||
sectionHeader.add(sectionName, BorderLayout.CENTER);
|
||||
|
||||
final JPanel sectionContents = new JPanel();
|
||||
sectionContents.setLayout(new DynamicGridLayout(0, 1, 0, 5));
|
||||
sectionContents.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
|
||||
sectionContents.setBorder(new CompoundBorder(
|
||||
new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR),
|
||||
new EmptyBorder(BORDER_OFFSET, 0, BORDER_OFFSET, 0)));
|
||||
sectionContents.setVisible(isOpen);
|
||||
section.add(sectionContents, BorderLayout.SOUTH);
|
||||
|
||||
// Add listeners to each part of the header so that it's easier to toggle them
|
||||
final MouseAdapter adapter = new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
toggleSection(csd, sectionToggle, sectionContents);
|
||||
}
|
||||
};
|
||||
sectionToggle.addActionListener(actionEvent -> toggleSection(csd, sectionToggle, sectionContents));
|
||||
sectionName.addMouseListener(adapter);
|
||||
sectionHeader.addMouseListener(adapter);
|
||||
|
||||
sectionWidgets.put(csd.getKey(), sectionContents);
|
||||
|
||||
topLevelPanels.put(csd, section);
|
||||
}
|
||||
|
||||
for (ConfigItemDescriptor cid : cd.getItems())
|
||||
{
|
||||
if (cid.getItem().hidden())
|
||||
@@ -427,9 +532,19 @@ class ConfigPanel extends PluginPanel
|
||||
item.add(button, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
mainPanel.add(item);
|
||||
JPanel section = sectionWidgets.get(cid.getItem().section());
|
||||
if (section == null)
|
||||
{
|
||||
topLevelPanels.put(cid, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
topLevelPanels.values().forEach(mainPanel::add);
|
||||
|
||||
JButton resetButton = new JButton("Reset");
|
||||
resetButton.addActionListener((e) ->
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user