From 89f6904604de5ba9048fc5570b470eb4554bf9af Mon Sep 17 00:00:00 2001 From: ceikry Date: Thu, 5 Aug 2021 14:58:24 -0500 Subject: [PATCH] Launcher now has a settings menu for the client. Pog. --- build.gradle | 1 + src/main/kotlin/Checkbox.kt | 19 ++ src/main/kotlin/ImgButton.kt | 2 +- src/main/kotlin/MainWindow.kt | 17 + src/main/kotlin/settingseditor/Json.kt | 112 +++++++ .../kotlin/settingseditor/SettingsWindow.kt | 303 ++++++++++++++++++ src/main/resources/config.json | 61 ++++ src/main/resources/misc.png | Bin 0 -> 749 bytes src/main/resources/rightClick.png | Bin 0 -> 836 bytes src/main/resources/save_hi.png | Bin 0 -> 470 bytes src/main/resources/save_lo.png | Bin 0 -> 434 bytes src/main/resources/settings.png | Bin 0 -> 508 bytes src/main/resources/toggleOff.png | Bin 0 -> 258 bytes src/main/resources/toggleOn.png | Bin 0 -> 414 bytes 14 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/Checkbox.kt create mode 100644 src/main/kotlin/settingseditor/Json.kt create mode 100644 src/main/kotlin/settingseditor/SettingsWindow.kt create mode 100644 src/main/resources/config.json create mode 100644 src/main/resources/misc.png create mode 100644 src/main/resources/rightClick.png create mode 100644 src/main/resources/save_hi.png create mode 100644 src/main/resources/save_lo.png create mode 100644 src/main/resources/settings.png create mode 100644 src/main/resources/toggleOff.png create mode 100644 src/main/resources/toggleOn.png diff --git a/build.gradle b/build.gradle index 1348b03..3eec34d 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" implementation 'org.jsoup:jsoup:1.14.1' + implementation files("libs/json-simple-1.1.1.jar") } jar { diff --git a/src/main/kotlin/Checkbox.kt b/src/main/kotlin/Checkbox.kt new file mode 100644 index 0000000..ef7c47f --- /dev/null +++ b/src/main/kotlin/Checkbox.kt @@ -0,0 +1,19 @@ +class Checkbox : ImgButton("/toggleOn.png","toggleOff.png", false) { + + var isToggled = false + set(value) { + field = value + isEnabled = value + } + + init { + isEnabled = false + + onClick { + isToggled = !isToggled + } + + onMouseEnter { } + onMouseExit { } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ImgButton.kt b/src/main/kotlin/ImgButton.kt index c049094..d0ea171 100644 --- a/src/main/kotlin/ImgButton.kt +++ b/src/main/kotlin/ImgButton.kt @@ -5,7 +5,7 @@ import javax.swing.Icon import javax.swing.ImageIcon import javax.swing.JLabel -class ImgButton(enabledURL: String, disabledURL: String = enabledURL, val autoHandleMouse: Boolean = true) : JLabel() { +open class ImgButton(enabledURL: String, disabledURL: String = enabledURL, val autoHandleMouse: Boolean = true) : JLabel() { private var hoverMethod: (MouseEvent) -> Unit = {} private var mouseLeaveMethod: (MouseEvent) -> Unit = {} diff --git a/src/main/kotlin/MainWindow.kt b/src/main/kotlin/MainWindow.kt index 48b706f..2f02fcd 100644 --- a/src/main/kotlin/MainWindow.kt +++ b/src/main/kotlin/MainWindow.kt @@ -1,4 +1,5 @@ import com.sun.java.accessibility.util.AWTEventMonitor +import settingseditor.SettingsWindow import java.awt.* import java.awt.event.MouseAdapter import java.awt.event.MouseEvent @@ -74,6 +75,7 @@ object MainWindow : JFrame("2009scape Launcher") { addBugReportButton(panel) addHighscoreButton(panel) addDiscordButton(panel) + addSettingsbutton(panel) addLogo(panel) add(panel) } @@ -185,6 +187,21 @@ object MainWindow : JFrame("2009scape Launcher") { panel.add(button) } + private fun addSettingsbutton(panel: BackgroundPanel){ + val button = ImgButton("/settings.png") + val label = BackgroundLabel("/messageBox.png", "Client
Settings") + add(label) + + label.placeAt(panel.width - 100, 40, 90, 56) + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + button.onClick { + SettingsWindow.open() + } + button.placeAt(panel.width - 75, 6, 30, 30) + panel.add(button) + } + private fun addCloseButton(panel: BackgroundPanel){ val button = ImgButton("/close_hi.png","/close_dark.png") button.onClick { exitProcess(0) } diff --git a/src/main/kotlin/settingseditor/Json.kt b/src/main/kotlin/settingseditor/Json.kt new file mode 100644 index 0000000..82837f2 --- /dev/null +++ b/src/main/kotlin/settingseditor/Json.kt @@ -0,0 +1,112 @@ +package settingseditor + +import org.json.simple.JSONObject +import org.json.simple.parser.JSONParser +import java.io.File +import java.io.FileReader +import java.io.FileWriter + +object Json { + + val HOME = System.getProperty("user.home") + val CONF = HOME + File.separator + "config.json" + var data: JSONObject = JSONObject() + + fun save(){ + val customization = data["customization"] as JSONObject + val xpDrops = customization["xpdrops"] as JSONObject + val slayer = customization["slayer"] as JSONObject + val rcm = customization["right_click_menu"] as JSONObject + val styles = rcm["styles"] as JSONObject + + val background = rcm["background"] as JSONObject + val title_bar = rcm["title_bar"] as JSONObject + val border = rcm["border"] as JSONObject + val debug = data["debug"] as JSONObject + + background["color"] = SettingsWindow.bgColorField.text + background["opacity"] = SettingsWindow.bgOpacityField.text + title_bar["color"] = SettingsWindow.titleColorField.text + title_bar["opacity"] = SettingsWindow.titleOpacityField.text + title_bar["font_color"] = SettingsWindow.titleFontColor.text + border["color"] = SettingsWindow.borderColor.text + border["opacity"] = SettingsWindow.borderOpacity.text + styles["rs3border"] = SettingsWindow.rs3Border.isToggled + debug["item_debug"] = SettingsWindow.itemDebugCheckbox.isToggled + debug["object_debug"] = SettingsWindow.objectDebugCheckbox.isToggled + debug["npc_debug"] = SettingsWindow.npcDebugCheckbox.isToggled + xpDrops["enabled"] = SettingsWindow.xpDropsEnabled.isToggled + xpDrops["drop_mode"] = SettingsWindow.xpDropMode.selectedIndex + xpDrops["track_mode"] = SettingsWindow.xpTrackMode.selectedIndex + slayer["enabled"] = SettingsWindow.slayerEnabled.isToggled + slayer["color"] = SettingsWindow.slayerColor.text + slayer["opacity"] = SettingsWindow.slayerOpacity.text + customization["login_theme"] = SettingsWindow.loginTheme.text + + FileWriter(CONF).use { writer -> + writer.write(data.toJSONString()) + writer.flush() + } + } + + fun parse(){ + try { + if(!File(CONF).exists()){ + FileReader(javaClass.getResource("/config.json")!!.file).use { reader -> + val writer = FileWriter(CONF) + reader.copyTo(writer, 1024) + writer.flush() + writer.close() + } + } + data = FileReader(CONF).use { reader -> + val parser = JSONParser() + parser.parse(reader) as JSONObject + } + + val customization = data["customization"] as JSONObject + val xpDrops = customization["xpdrops"] as JSONObject + val slayer = customization["slayer"] as JSONObject + val rcm = customization["right_click_menu"] as JSONObject + val styles = rcm["styles"] as JSONObject + + val background = rcm["background"] as JSONObject + val title_bar = rcm["title_bar"] as JSONObject + val border = rcm["border"] as JSONObject + val debug = data["debug"] as JSONObject + + SettingsWindow.itemDebugCheckbox.isToggled = debug["item_debug"] as Boolean + SettingsWindow.objectDebugCheckbox.isToggled = debug["object_debug"] as Boolean + SettingsWindow.npcDebugCheckbox.isToggled = debug["npc_debug"] as Boolean + + SettingsWindow.rs3Border.isToggled = styles["rs3border"] as Boolean + SettingsWindow.bgColorField.text = background["color"].toString() + SettingsWindow.bgOpacityField.text = background["opacity"].toString() + SettingsWindow.titleColorField.text = title_bar["color"].toString() + SettingsWindow.titleOpacityField.text = title_bar["opacity"].toString() + SettingsWindow.titleFontColor.text = title_bar["font_color"].toString() + SettingsWindow.borderColor.text = border["color"].toString() + SettingsWindow.borderOpacity.text = border["opacity"].toString() + + SettingsWindow.xpDropsEnabled.isToggled = xpDrops.getOrDefault("enabled",true) as Boolean + SettingsWindow.xpDropMode.selectedIndex = xpDrops.getOrDefault("drop_mode",0).toString().toInt() + SettingsWindow.xpTrackMode.selectedIndex = xpDrops.getOrDefault("track_mode",0).toString().toInt() + + SettingsWindow.slayerEnabled.isToggled = slayer.getOrDefault("enabled",true) as Boolean + SettingsWindow.slayerColor.text = slayer.getOrDefault("color", "#635a38").toString() + SettingsWindow.slayerOpacity.text = slayer.getOrDefault("opacity","180").toString() + SettingsWindow.loginTheme.text = customization.getOrDefault("login_theme","scape main").toString() + } catch (e: Exception) { + println("error parsing settings, replacing with defaults...") + e.printStackTrace() + File(CONF).delete() + FileReader(javaClass.getResource("/config.json")!!.file).use { reader -> + val writer = FileWriter(CONF) + reader.copyTo(writer, 1024) + writer.flush() + writer.close() + } + parse() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/settingseditor/SettingsWindow.kt b/src/main/kotlin/settingseditor/SettingsWindow.kt new file mode 100644 index 0000000..185724e --- /dev/null +++ b/src/main/kotlin/settingseditor/SettingsWindow.kt @@ -0,0 +1,303 @@ +package settingseditor + +import BackgroundLabel +import BackgroundPanel +import Checkbox +import ImgButton +import placeAt +import java.awt.BorderLayout +import java.awt.Color +import java.awt.Dimension +import java.awt.LayoutManager +import javax.imageio.ImageIO +import javax.swing.* + +object SettingsWindow : JFrame("Client Settings") { + + val xpDropModeOptions = arrayOf("instant","incremental") + val xpTrackModeOptions = arrayOf("total xp","recent skill") + + var tabs = ArrayList() + var buttons = ArrayList() + var tabNotes = ArrayList() + var activeTab = 0 + var buttonSpacer = 0 + val itemDebugCheckbox = Checkbox() + val objectDebugCheckbox = Checkbox() + val npcDebugCheckbox = Checkbox() + val rs3Border = Checkbox() + val bgColorField = JTextField() + val bgOpacityField = JTextField() + val titleColorField = JTextField() + val titleOpacityField = JTextField() + val titleFontColor = JTextField() + val borderColor = JTextField() + val borderOpacity = JTextField() + val loginTheme = JTextField() + val xpDropsEnabled = Checkbox() + val xpDropMode = JComboBox(xpDropModeOptions) + val xpTrackMode = JComboBox(xpTrackModeOptions) + val slayerColor = JTextField() + val slayerOpacity = JTextField() + val slayerEnabled = Checkbox() + + init { + isUndecorated = true + isVisible = false + isResizable = false + defaultCloseOperation = HIDE_ON_CLOSE + preferredSize = Dimension(400,300) + size = Dimension(400,300) + val tileBG = BackgroundPanel(ImageIO.read(javaClass.getResource("/topBar.png"))) + contentPane = tileBG + layout = null + setLocationRelativeTo(null) + + addDebugTab() + addRightClickTab() + addMiscTab() + + val saveButton = ImgButton("/save_hi.png","/save_lo.png") + saveButton.onClick { + Json.save() + isVisible = false + } + saveButton.placeAt(width - 30, height - 30, 30, 30) + add(saveButton) + } + + fun addMiscTab() { + val pane = getThemedPanel() + pane.layout = BoxLayout(pane, BoxLayout.PAGE_AXIS) + val button = ImgButton("/misc.png", "/misc.png", false) + + val label = BackgroundLabel("/messageBox.png", "Misc
Settings") + label.placeAt(40, 30, 90, 56) + add(label) + + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + + val xpTogglePanel = getThemedPanel(BorderLayout()) + val xpDropPanel = getThemedPanel(BorderLayout()) + val xpTrackPanel = getThemedPanel(BorderLayout()) + val slayerTogglePanel = getThemedPanel(BorderLayout()) + val slayerColorPanel = getThemedPanel(BorderLayout()) + val slayerOpacityPanel = getThemedPanel(BorderLayout()) + val loginThemePanel = getThemedPanel(BorderLayout()) + val xpToggleLabel = getLabel("XP Drops Enabled") + val xpDropLabel = getLabel("XP Drop Mode") + val xpTrackLabel = getLabel("XP Track Mode") + val slayerToggleLabel = getLabel("Slayer Tracker Enabled") + val slayerColorLabel = getLabel("Slayer Tracker Color") + val slayerOpacityLabel = getLabel("Slayer Tracker Opacity") + val loginThemeLabel = getLabel("Login Theme") + + for(field in arrayOf(loginTheme, xpDropMode, xpTrackMode, slayerColor, slayerOpacity)) + { + field.size = Dimension(200,10) + field.minimumSize = Dimension(200,10) + field.maximumSize = Dimension(200,10) + field.preferredSize = Dimension(200,10) + } + + loginThemePanel.add(loginThemeLabel, BorderLayout.WEST) + loginThemePanel.add(loginTheme, BorderLayout.EAST) + pane.add(loginThemePanel) + pane.add(getSeparator()) + + xpTogglePanel.add(xpToggleLabel, BorderLayout.WEST) + xpTogglePanel.add(xpDropsEnabled, BorderLayout.EAST) + pane.add(xpTogglePanel) + + xpDropPanel.add(xpDropLabel, BorderLayout.WEST) + xpDropPanel.add(xpDropMode, BorderLayout.EAST) + pane.add(xpDropPanel) + + xpTrackPanel.add(xpTrackLabel, BorderLayout.WEST) + xpTrackPanel.add(xpTrackMode, BorderLayout.EAST) + pane.add(xpTrackPanel) + pane.add(getSeparator()) + + slayerTogglePanel.add(slayerToggleLabel, BorderLayout.WEST) + slayerTogglePanel.add(slayerEnabled, BorderLayout.EAST) + pane.add(slayerTogglePanel) + + slayerColorPanel.add(slayerColorLabel, BorderLayout.WEST) + slayerColorPanel.add(slayerColor, BorderLayout.EAST) + pane.add(slayerColorPanel) + + slayerOpacityPanel.add(slayerOpacityLabel, BorderLayout.WEST) + slayerOpacityPanel.add(slayerOpacity, BorderLayout.EAST) + pane.add(slayerOpacityPanel) + + addTab(pane, button, getLabel("Theme: all lowercase")) + } + + fun addDebugTab(){ + val pane = getThemedPanel() + pane.layout = BoxLayout(pane, BoxLayout.PAGE_AXIS) + val button = ImgButton("/settings.png", "/settings.png", false) + + val label = BackgroundLabel("/messageBox.png", "Debug
Settings") + label.placeAt(0,30, 90, 56) + add(label) + + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + + val itemDebug = getThemedPanel(BorderLayout()) + val itemDebugLabel = getLabel("Item IDs Visible") + itemDebug.add(itemDebugLabel, BorderLayout.WEST) + itemDebug.add(itemDebugCheckbox, BorderLayout.EAST) + pane.add(itemDebug) + pane.add(getSeparator()) + + val objectDebug = getThemedPanel(BorderLayout()) + val objectDebugLabel = getLabel("Object IDs Visible") + objectDebug.add(objectDebugLabel, BorderLayout.WEST) + objectDebug.add(objectDebugCheckbox, BorderLayout.EAST) + pane.add(objectDebug) + pane.add(getSeparator()) + + val npcDebug = getThemedPanel(BorderLayout()) + val npcDebugLabel = getLabel("NPC IDs Visible") + npcDebug.add(npcDebugLabel, BorderLayout.WEST) + npcDebug.add(npcDebugCheckbox, BorderLayout.EAST) + pane.add(npcDebug) + + addTab(pane, button) + } + + fun addRightClickTab() { + val pane = getThemedPanel() + pane.layout = BoxLayout(pane, BoxLayout.PAGE_AXIS) + val button = ImgButton("/rightClick.png", "/rightClick.png", false) + + val label = BackgroundLabel("/messageBox.png", "Rightclick
Settings") + label.placeAt(5,30, 90, 56) + add(label) + + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + + val rs3BorderPanel = getThemedPanel(BorderLayout()) + val bgColorPanel = getThemedPanel(BorderLayout()) + val bgOpacityPanel = getThemedPanel(BorderLayout()) + val titleColorPanel = getThemedPanel(BorderLayout()) + val titleOpacityPanel = getThemedPanel(BorderLayout()) + val titleFontPanel = getThemedPanel(BorderLayout()) + val borderColorPanel = getThemedPanel(BorderLayout()) + val borderOpacityPanel = getThemedPanel(BorderLayout()) + val rs3BorderLabel = getLabel("Use RS3-Style Menu Border") + val bgColorLabel = getLabel("Background Color") + val bgOpacityLabel = getLabel("Background Opacity") + val titleColorLabel = getLabel("Title Bar Color") + val titleOpacityLabel = getLabel("Title Bar Opacity") + val titleFontLabel = getLabel("Title Bar Font Color") + val borderColorLabel = getLabel("Border Color") + val borderOpacityLabel = getLabel("Border Opacity") + + for(field in arrayOf(bgColorField, bgOpacityField, titleColorField, titleOpacityField, titleFontColor, borderColor, borderOpacity)) + { + field.size = Dimension(100,10) + field.minimumSize = Dimension(100,10) + field.maximumSize = Dimension(100,10) + field.preferredSize = Dimension(100,10) + } + + rs3BorderPanel.add(rs3BorderLabel, BorderLayout.WEST) + rs3BorderPanel.add(rs3Border, BorderLayout.EAST) + pane.add(rs3BorderPanel) + pane.add(getSeparator()) + + bgColorPanel.add(bgColorLabel, BorderLayout.WEST) + bgColorPanel.add(bgColorField, BorderLayout.EAST) + pane.add(bgColorPanel) + + bgOpacityPanel.add(bgOpacityLabel, BorderLayout.WEST) + bgOpacityPanel.add(bgOpacityField, BorderLayout.EAST) + pane.add(bgOpacityPanel) + pane.add(getSeparator()) + + titleColorPanel.add(titleColorLabel, BorderLayout.WEST) + titleColorPanel.add(titleColorField, BorderLayout.EAST) + pane.add(titleColorPanel) + + titleFontPanel.add(titleFontLabel, BorderLayout.WEST) + titleFontPanel.add(titleFontColor, BorderLayout.EAST) + pane.add(titleFontPanel) + + titleOpacityPanel.add(titleOpacityLabel, BorderLayout.WEST) + titleOpacityPanel.add(titleOpacityField, BorderLayout.EAST) + pane.add(titleOpacityPanel) + pane.add(getSeparator()) + + borderColorPanel.add(borderColorLabel, BorderLayout.WEST) + borderColorPanel.add(borderColor, BorderLayout.EAST) + pane.add(borderColorPanel) + + borderOpacityPanel.add(borderOpacityLabel, BorderLayout.WEST) + borderOpacityPanel.add(borderOpacity, BorderLayout.EAST) + pane.add(borderOpacityPanel) + + addTab(pane, button, getLabel("Color: HEX || Opacity: 0-255")) + } + + fun addTab(content: JPanel, button: ImgButton, tabNote: JLabel = getLabel("")){ + content.placeAt(0,30,width,height - 60) + add(content) + tabs.add(content) + content.isVisible = false + button.placeAt(buttonSpacer, 0, 30, 30) + button.onClick { activeTab = buttons.indexOf(button); updateVisibleTab() } + buttonSpacer += 35 + add(button) + button.isEnabled = false + buttons.add(button) + tabNote.placeAt(5, 275, width / 2, 20) + tabNote.isVisible = true + add(tabNote) + tabNotes.add(tabNote) + } + + fun open(){ + activeTab = 0 + updateVisibleTab() + Json.parse() + isVisible = true + } + + fun updateVisibleTab(){ + for(i in 0 until tabs.size){ + if(i != activeTab){ + tabs[i].isVisible = false + tabNotes[i].isVisible = false + } + else { + tabs[i].isVisible = true + tabNotes[i].isVisible = true + } + } + repaint() + } + + fun getThemedPanel(layout: LayoutManager? = null): JPanel{ + val panel = if(layout == null) JPanel() else JPanel(layout) + panel.background = Color(102,90,69) + return panel + } + + fun getSeparator(): JSeparator{ + val sep = JSeparator(JSeparator.HORIZONTAL) + sep.background = Color(57,49,39) + sep.foreground = Color(57,49,39) + return sep + } + + fun getLabel(text: String): JLabel{ + val label = JLabel(text) + label.foreground = Color(227,208,179) + return label + } +} \ No newline at end of file diff --git a/src/main/resources/config.json b/src/main/resources/config.json new file mode 100644 index 0000000..19c0684 --- /dev/null +++ b/src/main/resources/config.json @@ -0,0 +1,61 @@ +{ + "ip_address": "play.2009scape.org", + "ip_management" : "play.2009scape.org", + "server_port": 43594, + "wl_port": 5555, + "js5_port": 43593, + "world": 1, + + "customization": { + "login_theme": "scape main", + + "right_click_menu": { + "background": { + "color": "#5D5447", + "opacity": "255" + }, + "title_bar": { + "color": "#000000", + "opacity": "255", + "font_color": "#FFFFFF" + }, + "border": { + "color": "#FFFFFF", + "opacity": "255" + }, + "styles": { + "Presets provide default customizations.": "rs3, classic, or custom. custom allows you to define your own values above. Classic is standard 2009.", + "presets": "custom", + "rs3border": false + } + }, + "xpdrops": { + "enabled": true, + "drop_mode": 0, + "track_mode": 0 + }, + "slayer": { + "enabled": true, + "color": "#635a38", + "opacity": "180" + }, + "rendering_options": { + "technical": { + "render_distance_increase": true + }, + "skybox": { + "skybox_color" : "Coming in a future update..." + } + } + }, + + "debug": { + "item_debug": false, + "object_debug": false, + "npc_debug": false, + "hd_login_region_debug": false, + "hd_login_region_debug_verbose": false, + "cache_debug": false, + "world_map_debug": false + } +} diff --git a/src/main/resources/misc.png b/src/main/resources/misc.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffdd0e795bad919d9debed516cda7a8f0955f3 GIT binary patch literal 749 zcmV!sm> zazGz$Pw<7=k^tdvJOhltF~G4+uOT@0dB#gPL3I@Nf8CxE`{|-|IJWhR^A2BmJtTqv zKmkM-qzACbFkVw%9~JxPeDCnPi$i{Uu^&wilwLlRRn#$j<{xgJP%T(sQP-4h^Fs7mE^;#a zgz};YN^$aW+4x%bSQ)|16U9I70Y=zinP*+yU%0Y8^md#tPYuR0iI{s{;{7wj)~u`X zx_S@8#4W0fV`ugNfBLE-@QB67uWmq_;p$ydb2Pe(*9W%&fCGMDsUlO3R3 z-dW7Y^wKCz4@VsWi?ubp)%Y3pc0Jq8xK8k%zYU2tIA6BH^?ENH+?DYNUM&|h#jsq#x%VYoVJNVZ4cF00000NkvXXu0mjf5*uY< literal 0 HcmV?d00001 diff --git a/src/main/resources/rightClick.png b/src/main/resources/rightClick.png new file mode 100644 index 0000000000000000000000000000000000000000..802070867efe6b80fe1e8a75295b2dd67df09392 GIT binary patch literal 836 zcmV-K1H1f*P)?yH1YN|^QZ%@+iUEyH!~_xBihV3xRK#}`yC`CXqD7Iqa487giEiBJ zB5FaRB7y}$tYAw}>uXc7JO2RT$Tx7pWO{F2y|D@Wm=Gp&zWJSV=S&g`t?+Aie21(G zg(TEyuZDvH2)7yrreT0=n7RS&NhD-%Dk+RB2GiEoDjgl$B%R(T&vQAMo_=G-?(TyI z&J9=>m{=?(s{8lu7C`1d=Z$9GzmwOmUdWT|BbmH&TY57IiAUGUmi0}tu4#2JFzUq7 z!9fEs_4KiEVW7Xy1O@;Qg~>gelI+6=a_&fv%#5XFZZs}$hqoKBnqd@zpy~nvzs-L! z;yqwMBES^lY+u?ae{H|~nMlgtTS+r2RMjb=FkOgW3kyZ!v$>BSj3WwmtiMaLmwM#o zNJ?hMGX)q`)ldrJLM$%+Hsbxy?_UcpQ8S7$0f6Kt4R|u?Hihx z!i2+NbG}f33KWDZu0yOpes#qh@~j;N;{4G12@=Mxq06HejZ2hB%ciJz>i~#` z$YjzMGz{*rS=>Rx7tR|X-#!KqH3T`2C%Yl%EVkVG(PF+ial^o{>7k)BLM!s^V*t^o zXpl^%YvUA)>B0(D?fK)v;103McKJvoVqCJJ^nkR%Dgj|W8^Z0HVALfhQA!g%|i182e8t=6nT_N-U O0000RW(pIMpq{Ww@-Y?Ej%*I0zr# zFdc3%#fHNSe!HpZ|KVv_)OYpYE1MvOBNk``HaB_~iU*!WJk=iTppmd^*^G7wa0~2ImB5!wrA2a~1|ZJhc#PIL;sg zxfo_J%*DutgXlOH?f-L&y|H4PhJz&D9fAbK?^7!<3_dd@4a|~3 z7Unqf0Nik-um%|l!!X0wHO6Db%c?>tHyk<4k#Ufj_J0XBR^kjtHy9M&u)+(Z?%VNY zU>bxE&nP9FdqEbw-CB!bFerV441c(^1dJcAsQiC#c{P~c+mj4VhD4P)APX)|OZ$Im zekRyp5C(Y}WWbz!@Ba&-X?jj^Fh=?&s>}f?CIypRn87p M07*qoM6N<$g0mRUKL7v# literal 0 HcmV?d00001 diff --git a/src/main/resources/save_lo.png b/src/main/resources/save_lo.png new file mode 100644 index 0000000000000000000000000000000000000000..c3587e9ffe678a83bebbfc9a7b7341d33184d151 GIT binary patch literal 434 zcmV;j0ZsmiP)N6m&2D%p6;HGFBc$inD zhW%gMQ4GfOTMOa1A=MXmn8OT*1qZUhJsECbzLg5Z@R?-^D41ZF!wiQ72MB`_CjwLWXqyPW_07*qoM6N<$g7&nup#T5? literal 0 HcmV?d00001 diff --git a/src/main/resources/settings.png b/src/main/resources/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..5b9f947dfb9863560c216240baec294ee1e9f855 GIT binary patch literal 508 zcmVY5Qbf_F+ad40YyZM6pDfoA|hx~SPI4_jl?gEvCz&y3cH{pf|Uv?l14Cs0Sm#> zLOUUqSo%XoW;e4tdwXBHFmSiG``qk1?@m{j?+{=jY{XsHS$uwZ1>avkaMyed*rZ_X z_J@q8>w_q`fbw1yp5H#9(R`3_e783#;LXVnlsAexLcj`&Syt=EIxu@1F5vy;R>JZN zb6Sd-Od4WC3#CmN$@G{nn1D9a%3N4b{D~K8HVZ9-o2iiUF}QFt%`GkaKy9#*!hCDB z9%)X=K+x67ZV1!{AJw+tzHy;-Mkvn7_kr#~4d$7S4E4(38BI)1IgUV3{p?f*KCcz? zCgV!2>Kq@TE4h>m?puW7W^OgF?K-DaQlzuvtoj1xGkvgLn1#k+UPJNN1m#xa!K4!w z)YwPCNjsh?L1`@yT?&GD3q|u`(8%uKn%r2&4>~=ZjN5qJn-~Peq9S;EarCcn1?80| zXe!k&!KghJ6&;gE^uWKMZcnfs6B90&!G-U&PzFvF@!RpwXyxBnd_=FlOl0 y)(fVr>Dr?dq4wjRj%eV4{*UyuzqjM~UyWZw;4+O#h79Ha0000BQo)BB6EbI-!U-b*?@NwlD0ghzwyf27RdHC}4{)ST}epr%|9G7sAX$=Rqer zBOsZHeitc@&fY|Hpv*{fUx~b)mB|75Ow=}u1Cozz@LxRibl1{Zp?y6DHvj+t07*qo IM6N<$f_KDgr2qf` literal 0 HcmV?d00001 diff --git a/src/main/resources/toggleOn.png b/src/main/resources/toggleOn.png new file mode 100644 index 0000000000000000000000000000000000000000..5042fd49fa4c0f63a6e5e3261f96cc124ee23527 GIT binary patch literal 414 zcmV;P0b%}$P)3XZJ1spPCT$f6{@j{{;&(up0ui3FZ=Xv4Wh` z|4Wa~`(LxV^uKGdEv6wZ_7?wLooxT>YAJ&;x_4j(G_S7#8z3pmk7#!!H!0TnuO1`+pOb?Xo>pMdeQaYtrXk1*Kv+#t^1pJX zG*A&Eia1Gz+&#VdzW@(Ah8PIr4-+)ou!(^%&b&i$E~XR!07{$