├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── gradle.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── banner-social.png ├── banner.png ├── build.gradle ├── fonts └── FiraCode-Medium.ttf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ide_emojis ├── default │ ├── blframe.png │ ├── brframe.png │ ├── btabbed_frame.png │ ├── ctabbed_frame.png │ ├── ltab_corner.png │ ├── ltab_corner_selected.png │ ├── ltab_separator.png │ ├── ltab_separator_selected.png │ ├── ltabbed_frame.png │ ├── rtab_corner.png │ ├── rtab_corner_selected.png │ ├── rtab_separator.png │ ├── rtab_separator_selected.png │ ├── rtabbed_frame.png │ ├── tlframe.png │ ├── trframe.png │ ├── ttabbed_frame.png │ └── ttabbed_frame_selected.png ├── delete.png ├── intellij │ ├── selected_bl.png │ ├── selected_bottom.png │ ├── selected_br.png │ ├── selected_ml.png │ ├── selected_mr.png │ ├── selected_side_bottom.png │ ├── selected_side_middle.png │ ├── selected_side_top.png │ ├── selected_tl.png │ ├── selected_top.png │ └── selected_tr.png ├── select.png └── unselected.png ├── image_inputs ├── intellij_base.png └── welcome_text.png ├── keyboard_emojis ├── alt.png ├── alt_active.png ├── backspace.png ├── caps_lock.png ├── caps_lock_active.png ├── context.png ├── ctrl.png ├── ctrl_active.png ├── cursor.png ├── del.png ├── down.png ├── end.png ├── enter.png ├── enterl.png ├── enterr.png ├── fn.png ├── fn_active.png ├── home.png ├── icon.png ├── ins.png ├── left.png ├── padding.png ├── pg_down.png ├── pg_up.png ├── right.png ├── shift.png ├── shiftl.png ├── shiftr.png ├── space.png ├── spacec.png ├── spacel.png ├── spacer.png ├── tab.png └── up.png ├── logo.png ├── mockup-display.html ├── mockup.png ├── settings.gradle ├── src ├── main │ ├── antlr │ │ ├── JavaScriptLexer.g4 │ │ └── JavaScriptParser.g4 │ ├── java │ │ └── com │ │ │ └── uddernetworks │ │ │ └── emojide │ │ │ ├── data │ │ │ ├── BasicDatabaseManager.java │ │ │ ├── BasicSQLBinder.java │ │ │ ├── DatabaseManager.java │ │ │ ├── GenericMap.java │ │ │ ├── SQLBinder.java │ │ │ ├── SQLBound.java │ │ │ └── document │ │ │ │ ├── BasicDocument.java │ │ │ │ ├── DefaultDocumentManager.java │ │ │ │ ├── Document.java │ │ │ │ └── DocumentManager.java │ │ │ ├── discord │ │ │ ├── DefaultDocumentTabController.java │ │ │ ├── DocumentTabController.java │ │ │ ├── ImageConverter.java │ │ │ ├── commands │ │ │ │ ├── CommandHelp.java │ │ │ │ ├── EmojiCommand.java │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── IDECommand.java │ │ │ │ ├── PurgeCommand.java │ │ │ │ ├── choosable │ │ │ │ │ ├── ChoosingList.java │ │ │ │ │ ├── ChoosingListManager.java │ │ │ │ │ ├── DefaultChoosingList.java │ │ │ │ │ └── DefaultChoosingListManager.java │ │ │ │ └── manager │ │ │ │ │ ├── Argument.java │ │ │ │ │ ├── ArgumentError.java │ │ │ │ │ ├── ArgumentList.java │ │ │ │ │ ├── Command.java │ │ │ │ │ ├── CommandArg.java │ │ │ │ │ ├── CommandManager.java │ │ │ │ │ ├── CommandResult.java │ │ │ │ │ └── EmbedUtils.java │ │ │ ├── emoji │ │ │ │ ├── DefaultEmojiManager.java │ │ │ │ ├── Emoji.java │ │ │ │ ├── EmojiManager.java │ │ │ │ ├── Group.java │ │ │ │ └── StaticEmoji.java │ │ │ └── font │ │ │ │ ├── DefaultFontManager.java │ │ │ │ ├── Font.java │ │ │ │ └── FontManager.java │ │ │ ├── event │ │ │ ├── Cancellable.java │ │ │ ├── Event.java │ │ │ ├── EventRaiser.java │ │ │ ├── Handler.java │ │ │ ├── Priority.java │ │ │ └── Raisable.java │ │ │ ├── generator │ │ │ ├── DefaultEmojiGenerator.java │ │ │ ├── EmojiGenerator.java │ │ │ └── LetterGenerator.java │ │ │ ├── gui │ │ │ ├── CustomRenderedContainerFrame.java │ │ │ ├── EditableDynamicTextFrame.java │ │ │ ├── EditableStaticTextFrame.java │ │ │ ├── EmptyContainerFrame.java │ │ │ ├── HighlightedTextFrame.java │ │ │ ├── StaticTextFrame.java │ │ │ ├── TextPromptFrame.java │ │ │ ├── WelcomeFrame.java │ │ │ ├── components │ │ │ │ ├── CachedDisplayer.java │ │ │ │ ├── ComponentUtils.java │ │ │ │ ├── DefaultEmojiComponent.java │ │ │ │ ├── DefaultEmojiContainer.java │ │ │ │ ├── DefaultPositionedComponent.java │ │ │ │ ├── Displayer.java │ │ │ │ ├── EmojiComponent.java │ │ │ │ ├── EmojiContainer.java │ │ │ │ ├── InvalidComponentException.java │ │ │ │ ├── MockupImageDisplayer.java │ │ │ │ ├── MockupWebpageDisplayer.java │ │ │ │ ├── PositionedComponent.java │ │ │ │ ├── output │ │ │ │ │ ├── DefaultOutputFrame.java │ │ │ │ │ ├── IntelliJOutputFrame.java │ │ │ │ │ ├── OutputFrame.java │ │ │ │ │ └── OutputFrameTheme.java │ │ │ │ ├── styled │ │ │ │ │ ├── StyleUtils.java │ │ │ │ │ ├── StyledEmojiComponent.java │ │ │ │ │ └── StyledEmojiContainer.java │ │ │ │ └── theme │ │ │ │ │ ├── ThemeDependantRendering.java │ │ │ │ │ └── ThemeImplementor.java │ │ │ ├── render │ │ │ │ ├── RenderAction.java │ │ │ │ ├── RenderBreakpoint.java │ │ │ │ ├── RenderEngine.java │ │ │ │ └── RenderEntry.java │ │ │ ├── tabbed │ │ │ │ ├── DefaultTabbedFrame.java │ │ │ │ ├── IntelliJTabbedFrame.java │ │ │ │ ├── TabbedFrame.java │ │ │ │ ├── TabbedFrameConstants.java │ │ │ │ └── TabbedFrameTheme.java │ │ │ ├── tabs │ │ │ │ └── Tab.java │ │ │ ├── text │ │ │ │ ├── AutoGrowArrayList.java │ │ │ │ ├── DefaultTextBlock.java │ │ │ │ ├── DynamicTextBlock.java │ │ │ │ └── TextBlock.java │ │ │ └── theme │ │ │ │ ├── DefaultThemeManager.java │ │ │ │ ├── Theme.java │ │ │ │ └── ThemeManager.java │ │ │ ├── ide │ │ │ ├── ConsolePiper.java │ │ │ ├── FunctionController.java │ │ │ ├── LanguageHighlighter.java │ │ │ └── lexer │ │ │ │ └── javascript │ │ │ │ ├── JavaScriptBaseLexer.java │ │ │ │ └── JavaScriptBaseParser.java │ │ │ ├── keyboard │ │ │ ├── KeyPressEvent.java │ │ │ ├── KeyboardInputManager.java │ │ │ ├── KeyboardRaisable.java │ │ │ └── SimpleKeyboardInputManager.java │ │ │ ├── main │ │ │ ├── ChoosableEnum.java │ │ │ ├── ConfigManager.java │ │ │ ├── CustomPool.java │ │ │ ├── DefaultConfigManager.java │ │ │ ├── EmojIDE.java │ │ │ └── Thread.java │ │ │ ├── utils │ │ │ └── Commandline.java │ │ │ └── web │ │ │ ├── BasicWebCallback.java │ │ │ ├── BasicWebCallbackHandler.java │ │ │ ├── CommandCallback.java │ │ │ ├── SimpleWebListener.java │ │ │ ├── WebCallback.java │ │ │ ├── WebCallbackHandler.java │ │ │ └── WebListener.java │ └── resources │ │ ├── config.conf │ │ ├── createDocument.sql │ │ ├── documents.sql │ │ ├── getDocument.sql │ │ ├── getDocuments.sql │ │ ├── log4j.properties │ │ ├── log4j.xml │ │ ├── modifyDocument.sql │ │ └── removeDocument.sql └── test │ └── java │ └── com │ └── uddernetworks │ └── emojide │ └── gui │ └── text │ └── AutoGrowArrayListTest.java └── welcome_emojis └── name ├── 0w0.png ├── 0w1.png ├── 1w0.png ├── 1w1.png ├── 2w0.png ├── 2w1.png ├── 3w0.png ├── 3w1.png ├── 4w0.png ├── 4w1.png ├── 5w0.png ├── 5w1.png ├── 6w0.png ├── 6w1.png ├── 7w0.png └── 7w1.png /.gitattributes: -------------------------------------------------------------------------------- 1 | mockup-display.html linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/RubbaBoy'] 13 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | Ubuntu: 10 | name: Run Ubuntu 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Set up JDK 12 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 12 18 | - name: Print gradle 19 | run: | 20 | chmod +x gradlew 21 | ./gradlew --version 22 | - name: Test 23 | run: ./gradlew test 24 | - name: Clone Javadoc 25 | run: | 26 | git config --global user.email "nobody@ms-paint-i.de" 27 | git config --global user.name "GitHub Actions" 28 | mkdir pages 29 | cd pages 30 | git clone https://github.com/RubbaBoy/EmojIDE-javadocs . 31 | rm -rf * 32 | echo Javadocs cloned to: 33 | pwd 34 | cd ../ 35 | - name: Javadoc 36 | run: | 37 | ./gradlew javadoc 38 | cd build/docs 39 | mv javadoc/* ../../pages 40 | cd ../../pages 41 | echo "docs.xn--is8hfy.ws" > CNAME 42 | - name: Push Javadoc 43 | run: | 44 | cd pages 45 | git add . 46 | git commit -m "Update docs from RubbaBoy/EmojIDE" 47 | git push "https://${{ secrets.TOKEN }}@github.com/RubbaBoy/EmojIDE-javadocs.git" master:master 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | emojis/ 4 | build/ 5 | generated_emojis/ 6 | secret.conf 7 | *.log 8 | executing/ 9 | database/ 10 | ide_emojis/intellij/gen 11 | *.pdn 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: windows 2 | language: shell 3 | filter_secrets: false 4 | cache: false 5 | before_install: 6 | - choco install openjdk -y 7 | - wget http://services.gradle.org/distributions/gradle-5.2.1-bin.zip 8 | - mkdir 'c:\\emojide' 9 | - unzip -qq gradle-5.2.1-bin.zip -d /c/emojide/gradle 10 | - export GRADLE_HOME=/c/emojide/gradle/gradle-5.2.1 11 | - export JAVA_HOME="C:\\Program Files\\OpenJDK\\jdk-12.0.2" 12 | - export PATH=$GRADLE_HOME/bin:$PATH 13 | - export PATH=$JAVA_HOME/bin:$PATH 14 | - set TERM=dumb 15 | script: 16 | - gradle clean install cleanTest test --no-daemon 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adam Yarris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | HitCount 3 | Stars 4 | GitHub issues 5 | GitHub issues 6 |

7 |

8 | EmojIDE. Pictures are worth a thousand words... So why not write code in them? 9 |

10 |

11 | 12 | Wiki | 13 | Docs | 14 | Reddit | 15 | Demo | 16 | Hackathon 17 | 18 |

19 | 20 | 21 | 22 | After completing [MS Paint IDE](https://ms-paint-i.de/), I realized that moving from Discord to talk with people all the way to Paint to program was relatively exhausting. I then set to make EmojIDE, an IDE entirely in Discord Emojis. This project strives to increase communication among developers by making Discord the cool to both program in and communicate with other developers. 23 | 24 | ![EmojIDE screenshot](https://github.com/RubbaBoy/EmojIDE/wiki/screenshots/intellij_welcome.png) 25 | 26 | EmojIDE in the IntelliJ theme 27 | 28 | ## Features 29 | 30 | EmojIDE is a Discord bot created for the [r/ProgrammerHumor](https://www.reddit.com/r/ProgrammerHumor/) [hackathon](https://www.programmerhumor.org/Hackathon), lasting a month. This means that the bot does currently lack some features revolving around multi-user usage, and I am trying to use the time limit as justification for my laziness. The current features actually implemented in the bot, however, are: 31 | 32 | - 2050+ emojis across 42 Discord servers 33 | - Including 2 fonts (With expandability) in 8 color variations each 34 | - Easy inspectability, bulk deletion, and other means of management of emojis 35 | - Never-before-seen I/O, allowing for: 36 | - No reactions for any primary Emoji I/O 37 | - A full ANSI keyboard in emojis, with < 1s response time (Not including render engine throttles) 38 | - Help menus/embeds navigable via clicking on options 39 | - Selectable elements of an enum/list via emojis for settings 40 | - High expandability 41 | - A highly advanced Emoji based rendering framework 42 | - Highly expandable with little effort 43 | - Advanced theming capabilities 44 | - Current themes are EmojIDE and IntelliJ 45 | - A render engine to handle Discord API interactions 46 | - 3 Displayer implementations, allowing for: 47 | - Normal emoji rendering in a channel 48 | - Rendering immediately to an image (As rapid development can be bottlenecked by Discord's rate limiting) 49 | - Rendering to a local Discord clone for an exact preview immediately 50 | - Advanced, custom command framework 51 | - Custom event framework 52 | - HSQLDB Integration to store code files 53 | - Execution and full syntax highlighting of JavaScript 54 | 55 | ## Demo 56 | 57 | Here's a demo video of starting the IDE, changing some stuff, and writing a small program: 58 | 59 | [![EmojIDE Demo Video](https://github.com/RubbaBoy/EmojIDE/wiki/screenshots/thumbnail.png)](https://youtu.be/06pMgnB6e6o) -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | There's only one released version, so it's the only supported one. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.0.0 | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | If you find something that looks like it could cause some issues, either contact me via email at [adam@yarr.is](mailto:adam@yarr.is) or on Discord, `RubbaBoy#2832`. This doesn't store any data aside from the data you type in it and your Discord user ID, and can be deleted in the app via the Control + X keybind in the IDE. Any and all security issues will be addressed though. 14 | -------------------------------------------------------------------------------- /banner-social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/banner-social.png -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/banner.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'maven' 4 | id 'application' 5 | id 'antlr' 6 | } 7 | 8 | group 'com.uddernetworks.emojide' 9 | version '1.0.0' 10 | 11 | sourceCompatibility = 12 12 | 13 | mainClassName = 'com.uddernetworks.emojide.main.EmojIDE' 14 | 15 | repositories { 16 | mavenCentral() 17 | jcenter() 18 | } 19 | 20 | dependencies { 21 | testImplementation('org.junit.jupiter:junit-jupiter:5.4.2') 22 | implementation 'net.dv8tion:JDA:4.BETA.0_32' 23 | 24 | implementation 'mysql:mysql-connector-java:5.1.6' 25 | implementation 'com.zaxxer:HikariCP:3.3.1' 26 | implementation 'org.hsqldb:hsqldb:2.4.1' 27 | 28 | implementation 'org.slf4j:slf4j-api:1.7.25' 29 | implementation 'org.slf4j:slf4j-log4j12:1.7.25' 30 | 31 | implementation 'org.apache.commons:commons-text:1.7' 32 | implementation 'com.github.jhg023:SimpleNet:1.4.14' 33 | implementation 'com.electronwill.night-config:hocon:3.6.0' 34 | implementation 'commons-collections:commons-collections:3.2.2' 35 | 36 | implementation 'org.antlr:antlr4-runtime:4.7.2' 37 | antlr 'org.antlr:antlr4:4.7.2' 38 | } 39 | -------------------------------------------------------------------------------- /fonts/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/fonts/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 02 10:08:44 EDT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ide_emojis/default/blframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/blframe.png -------------------------------------------------------------------------------- /ide_emojis/default/brframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/brframe.png -------------------------------------------------------------------------------- /ide_emojis/default/btabbed_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/btabbed_frame.png -------------------------------------------------------------------------------- /ide_emojis/default/ctabbed_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ctabbed_frame.png -------------------------------------------------------------------------------- /ide_emojis/default/ltab_corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ltab_corner.png -------------------------------------------------------------------------------- /ide_emojis/default/ltab_corner_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ltab_corner_selected.png -------------------------------------------------------------------------------- /ide_emojis/default/ltab_separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ltab_separator.png -------------------------------------------------------------------------------- /ide_emojis/default/ltab_separator_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ltab_separator_selected.png -------------------------------------------------------------------------------- /ide_emojis/default/ltabbed_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ltabbed_frame.png -------------------------------------------------------------------------------- /ide_emojis/default/rtab_corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/rtab_corner.png -------------------------------------------------------------------------------- /ide_emojis/default/rtab_corner_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/rtab_corner_selected.png -------------------------------------------------------------------------------- /ide_emojis/default/rtab_separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/rtab_separator.png -------------------------------------------------------------------------------- /ide_emojis/default/rtab_separator_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/rtab_separator_selected.png -------------------------------------------------------------------------------- /ide_emojis/default/rtabbed_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/rtabbed_frame.png -------------------------------------------------------------------------------- /ide_emojis/default/tlframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/tlframe.png -------------------------------------------------------------------------------- /ide_emojis/default/trframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/trframe.png -------------------------------------------------------------------------------- /ide_emojis/default/ttabbed_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ttabbed_frame.png -------------------------------------------------------------------------------- /ide_emojis/default/ttabbed_frame_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/default/ttabbed_frame_selected.png -------------------------------------------------------------------------------- /ide_emojis/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/delete.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_bl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_bl.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_bottom.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_br.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_ml.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_mr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_mr.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_side_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_side_bottom.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_side_middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_side_middle.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_side_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_side_top.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_tl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_tl.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_top.png -------------------------------------------------------------------------------- /ide_emojis/intellij/selected_tr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/intellij/selected_tr.png -------------------------------------------------------------------------------- /ide_emojis/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/select.png -------------------------------------------------------------------------------- /ide_emojis/unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/ide_emojis/unselected.png -------------------------------------------------------------------------------- /image_inputs/intellij_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/image_inputs/intellij_base.png -------------------------------------------------------------------------------- /image_inputs/welcome_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/image_inputs/welcome_text.png -------------------------------------------------------------------------------- /keyboard_emojis/alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/alt.png -------------------------------------------------------------------------------- /keyboard_emojis/alt_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/alt_active.png -------------------------------------------------------------------------------- /keyboard_emojis/backspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/backspace.png -------------------------------------------------------------------------------- /keyboard_emojis/caps_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/caps_lock.png -------------------------------------------------------------------------------- /keyboard_emojis/caps_lock_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/caps_lock_active.png -------------------------------------------------------------------------------- /keyboard_emojis/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/context.png -------------------------------------------------------------------------------- /keyboard_emojis/ctrl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/ctrl.png -------------------------------------------------------------------------------- /keyboard_emojis/ctrl_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/ctrl_active.png -------------------------------------------------------------------------------- /keyboard_emojis/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/cursor.png -------------------------------------------------------------------------------- /keyboard_emojis/del.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/del.png -------------------------------------------------------------------------------- /keyboard_emojis/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/down.png -------------------------------------------------------------------------------- /keyboard_emojis/end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/end.png -------------------------------------------------------------------------------- /keyboard_emojis/enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/enter.png -------------------------------------------------------------------------------- /keyboard_emojis/enterl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/enterl.png -------------------------------------------------------------------------------- /keyboard_emojis/enterr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/enterr.png -------------------------------------------------------------------------------- /keyboard_emojis/fn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/fn.png -------------------------------------------------------------------------------- /keyboard_emojis/fn_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/fn_active.png -------------------------------------------------------------------------------- /keyboard_emojis/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/home.png -------------------------------------------------------------------------------- /keyboard_emojis/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/icon.png -------------------------------------------------------------------------------- /keyboard_emojis/ins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/ins.png -------------------------------------------------------------------------------- /keyboard_emojis/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/left.png -------------------------------------------------------------------------------- /keyboard_emojis/padding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/padding.png -------------------------------------------------------------------------------- /keyboard_emojis/pg_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/pg_down.png -------------------------------------------------------------------------------- /keyboard_emojis/pg_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/pg_up.png -------------------------------------------------------------------------------- /keyboard_emojis/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/right.png -------------------------------------------------------------------------------- /keyboard_emojis/shift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/shift.png -------------------------------------------------------------------------------- /keyboard_emojis/shiftl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/shiftl.png -------------------------------------------------------------------------------- /keyboard_emojis/shiftr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/shiftr.png -------------------------------------------------------------------------------- /keyboard_emojis/space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/space.png -------------------------------------------------------------------------------- /keyboard_emojis/spacec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/spacec.png -------------------------------------------------------------------------------- /keyboard_emojis/spacel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/spacel.png -------------------------------------------------------------------------------- /keyboard_emojis/spacer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/spacer.png -------------------------------------------------------------------------------- /keyboard_emojis/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/tab.png -------------------------------------------------------------------------------- /keyboard_emojis/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/keyboard_emojis/up.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/logo.png -------------------------------------------------------------------------------- /mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/mockup.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'EmojIDE' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/BasicSQLBinder.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.Arrays; 10 | import java.util.Objects; 11 | import java.util.stream.Collectors; 12 | 13 | public class BasicSQLBinder implements SQLBinder { 14 | 15 | private static Logger LOGGER = LoggerFactory.getLogger(BasicSQLBinder.class); 16 | 17 | @Override 18 | public void createBindings(Object object) { 19 | Arrays.stream(object.getClass().getDeclaredFields()) 20 | .parallel() 21 | .filter(field -> field.isAnnotationPresent(SQLBound.class)) 22 | .filter(field -> field.getType().equals(String.class)) 23 | .forEach(field -> { 24 | var sqlBoundFile = field.getAnnotation(SQLBound.class).file(); 25 | var sqlFile = (sqlBoundFile.isBlank() ? field.getName() : sqlBoundFile) + ".sql"; 26 | field.setAccessible(true); 27 | try { 28 | field.set(object, getQuery(sqlFile)); 29 | } catch (IllegalAccessException | IOException e) { 30 | LOGGER.error("Error setting field " + field.getName() + " in " + object.getClass().getSimpleName(), e); 31 | } 32 | }); 33 | } 34 | 35 | @Override 36 | public String getQuery(String name) throws IOException { 37 | LOGGER.info("Resource {}", name); 38 | var resource = Objects.requireNonNull(getClass().getClassLoader().getResource(name)); 39 | 40 | try (var reader = new BufferedReader(new InputStreamReader(resource.openStream()))) { 41 | return reader.lines().collect(Collectors.joining("\n")); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/DatabaseManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data; 2 | 3 | import com.uddernetworks.emojide.data.document.Document; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface DatabaseManager { 11 | 12 | /** 13 | * Initializes tables and queries for the DatabaseManager. 14 | */ 15 | void init(); 16 | 17 | /** 18 | * Gets a {@link Connection} from HikariCP. 19 | * 20 | * @return A {@link Connection} 21 | * @throws SQLException If bad stuff happens 22 | */ 23 | Connection getConnection() throws SQLException; 24 | 25 | /** 26 | * Gets all {@link Document}s in the database. 27 | * 28 | * @return All {@link Document}s 29 | */ 30 | List getAllDocuments(); 31 | 32 | /** 33 | * Gets the {@link Document} with the given unique name, if it exists. 34 | * 35 | * @param name The name to fetch 36 | * @return The {@link Document}, if existent 37 | */ 38 | Optional getDocument(String name); 39 | 40 | /** 41 | * Creates and inserts a given {@link Document}. 42 | * 43 | * @param document The {@link Document} to insert 44 | */ 45 | void insertDocument(Document document); 46 | 47 | /** 48 | * Inserts a given {@link Document}'s contents to the database. 49 | * 50 | * @param document The {@link Document} to get the content of 51 | */ 52 | void updateDocument(Document document); 53 | 54 | /** 55 | * Removes a given {@link Document} from the database. 56 | * 57 | * @param document The {@link Document} to remove 58 | */ 59 | void removeDocument(Document document); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/GenericMap.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * This is some sketchy shit that really shouldn't be used anywhere, but is. 7 | * This class assumes whatever you get from it is whatever type you want, without checking anything. Ths is prone to 8 | * cause errors. 9 | */ 10 | class GenericMap extends HashMap { 11 | public T get(String key) { 12 | return (T) super.get(key); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/SQLBinder.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data; 2 | 3 | import java.io.IOException; 4 | 5 | public interface SQLBinder { 6 | 7 | /** 8 | * Binds all fields from a given object with the annotation {@link SQLBound} to their respectiveSQL: file contents. 9 | * 10 | * @param object The instance to set fields to 11 | */ 12 | void createBindings(Object object); 13 | 14 | /** 15 | * Gets the string query from the resource file given. 16 | * 17 | * @param name The resource file to read 18 | * @return The string contents of it 19 | * @throws IOException If there are issues when creating/accessing the pool 20 | */ 21 | String getQuery(String name) throws IOException; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/SQLBound.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.FIELD}) 10 | public @interface SQLBound { 11 | String file() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/document/BasicDocument.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data.document; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class BasicDocument implements Document { 7 | 8 | private static Logger LOGGER = LoggerFactory.getLogger(BasicDocument.class); 9 | 10 | private DocumentManager documentManager; 11 | 12 | private final String name; 13 | private final long author; 14 | private String content; 15 | 16 | public BasicDocument(DocumentManager documentManager, String name, long author) { 17 | this(documentManager, name, author, ""); 18 | } 19 | 20 | public BasicDocument(DocumentManager documentManager, String name, long author, String content) { 21 | this.documentManager = documentManager; 22 | this.name = name; 23 | this.author = author; 24 | this.content = content; 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | @Override 33 | public long getAuthor() { 34 | return author; 35 | } 36 | 37 | @Override 38 | public String getContent() { 39 | return content; 40 | } 41 | 42 | @Override 43 | public void setContent(String content) { 44 | this.content = content; 45 | documentManager.updateDocument(this); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/document/DefaultDocumentManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data.document; 2 | 3 | import com.uddernetworks.emojide.data.DatabaseManager; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | public class DefaultDocumentManager implements DocumentManager { 12 | 13 | private static Logger LOGGER = LoggerFactory.getLogger(DefaultDocumentManager.class); 14 | 15 | private DatabaseManager databaseManager; 16 | 17 | public DefaultDocumentManager(DatabaseManager databaseManager) { 18 | this.databaseManager = databaseManager; 19 | } 20 | 21 | @Override 22 | public CompletableFuture> getAllDocuments() { 23 | return CompletableFuture.supplyAsync(() -> databaseManager.getAllDocuments()); 24 | } 25 | 26 | @Override 27 | public CompletableFuture> getDocument(String name) { 28 | return CompletableFuture.supplyAsync(() -> databaseManager.getDocument(name)); 29 | } 30 | 31 | @Override 32 | public CompletableFuture createDocument(String name, long authorId) { 33 | return CompletableFuture.supplyAsync(() -> { 34 | var document = new BasicDocument(this, name, authorId); 35 | databaseManager.insertDocument(document); 36 | return document; 37 | }); 38 | } 39 | 40 | @Override 41 | public CompletableFuture updateDocument(Document document) { 42 | return CompletableFuture.runAsync(() -> databaseManager.updateDocument(document)); 43 | } 44 | 45 | @Override 46 | public CompletableFuture removeDocument(Document document) { 47 | return CompletableFuture.runAsync(() -> databaseManager.removeDocument(document)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/document/Document.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data.document; 2 | 3 | public interface Document { 4 | 5 | /** 6 | * Gets the unique name of the document. 7 | * 8 | * @return The name of the document 9 | */ 10 | String getName(); 11 | 12 | /** 13 | * Gets the Discord ID of the document's creator. 14 | * 15 | * @return The creator of the document 16 | */ 17 | long getAuthor(); 18 | 19 | /** 20 | * Gets the contents of the document. 21 | * 22 | * @return The contents of the document 23 | */ 24 | String getContent(); 25 | 26 | /** 27 | * Sets the content of the document, also updating any {@link DocumentManager} of the action. 28 | * 29 | * @param content The new contents of the document 30 | */ 31 | void setContent(String content); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/data/document/DocumentManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.data.document; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | public interface DocumentManager { 8 | 9 | /** 10 | * Gets all {@link Document}s in the database. This is not cached due to the relative slowness of the IDE, so this 11 | * will not be called many times. See implementation for exceptions that may occur with this rule. 12 | * 13 | * @return All {@link Document}s 14 | */ 15 | CompletableFuture> getAllDocuments(); 16 | 17 | /** 18 | * Gets the {@link Document} with the given unique name, if it exists. 19 | * 20 | * @param name The name to fetch 21 | * @return The {@link Document}, if existent 22 | */ 23 | CompletableFuture> getDocument(String name); 24 | 25 | /** 26 | * Creates and inserts a {@link Document} with the given parameters and with empty content. 27 | * 28 | * @param name The name of the {@link Document} 29 | * @param authorId The author's Discord ID of the {@link Document} 30 | * @return The {@link Document}'s {@link CompletableFuture} 31 | */ 32 | CompletableFuture createDocument(String name, long authorId); 33 | 34 | /** 35 | * Inserts a given {@link Document}'s contents to the database. 36 | * 37 | * @param document The {@link Document} to get the content of 38 | * @return The {@link CompletableFuture} of the task 39 | */ 40 | CompletableFuture updateDocument(Document document); 41 | 42 | /** 43 | * Removes a given {@link Document} from the database. 44 | * 45 | * @param document The {@link Document} to remove 46 | * @return The {@link CompletableFuture} of the task 47 | */ 48 | CompletableFuture removeDocument(Document document); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/DefaultDocumentTabController.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord; 2 | 3 | import com.uddernetworks.emojide.data.document.Document; 4 | import com.uddernetworks.emojide.gui.EmptyContainerFrame; 5 | import com.uddernetworks.emojide.gui.HighlightedTextFrame; 6 | import com.uddernetworks.emojide.gui.StaticTextFrame; 7 | import com.uddernetworks.emojide.gui.components.Displayer; 8 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 9 | import com.uddernetworks.emojide.gui.components.output.OutputFrame; 10 | import com.uddernetworks.emojide.gui.components.theme.ThemeDependantRendering; 11 | import com.uddernetworks.emojide.gui.tabbed.TabbedFrame; 12 | import com.uddernetworks.emojide.gui.tabs.Tab; 13 | import com.uddernetworks.emojide.ide.ConsolePiper; 14 | import com.uddernetworks.emojide.ide.FunctionController; 15 | import com.uddernetworks.emojide.main.EmojIDE; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.Optional; 22 | 23 | import static com.uddernetworks.emojide.gui.tabbed.TabbedFrameConstants.AVAILABLE_TEXT_HEIGHT; 24 | 25 | public class DefaultDocumentTabController implements DocumentTabController { 26 | 27 | private static Logger LOGGER = LoggerFactory.getLogger(DefaultDocumentTabController.class); 28 | 29 | private EmojIDE emojIDE; 30 | private Displayer displayer; 31 | private TabbedFrame tabbedFrame; 32 | private StaticTextFrame outputFrame; 33 | private Map documents = new HashMap<>(); 34 | 35 | public DefaultDocumentTabController(EmojIDE emojIDE, Displayer displayer, TabbedFrame tabbedFrame) { 36 | this.emojIDE = emojIDE; 37 | this.displayer = displayer; 38 | this.tabbedFrame = tabbedFrame; 39 | 40 | outputFrame = new StaticTextFrame(displayer, 54, 4).setText(""); 41 | new FunctionController(emojIDE, displayer, tabbedFrame, new ConsolePiper(outputFrame)); 42 | } 43 | 44 | @Override 45 | public void addTab(Document document) { 46 | int textHeight = ThemeDependantRendering.getThemeConstant(TabbedFrame.class, AVAILABLE_TEXT_HEIGHT); 47 | var highlightFrame = new HighlightedTextFrame(displayer, 54, textHeight - 1, document.getContent()); 48 | highlightFrame.getTextBlock().onChange(document::setContent); 49 | 50 | var component = new EmptyContainerFrame(displayer, 56, textHeight + 6) 51 | .addChild(highlightFrame, 1, 1) 52 | .addChild(new OutputFrame(displayer, 56, 5).setOutput(outputFrame), 0, textHeight + 1); 53 | 54 | documents.put(component, document); 55 | tabbedFrame.addTab(document.getName(), component, true); 56 | tabbedFrame.refresh(); 57 | } 58 | 59 | @Override 60 | public void removeTab(Tab tab) { 61 | Optional.ofNullable(documents.get(tab.getComponent())).ifPresent(document -> { 62 | tabbedFrame.removeTab(tab); 63 | emojIDE.getDocumentManager().removeDocument(document); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/DocumentTabController.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord; 2 | 3 | import com.uddernetworks.emojide.data.document.Document; 4 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 5 | import com.uddernetworks.emojide.gui.tabbed.TabbedFrame; 6 | import com.uddernetworks.emojide.gui.tabs.Tab; 7 | 8 | public interface DocumentTabController { 9 | 10 | /** 11 | * Adds the given {@link Document} to the {@link TabbedFrame}. This listens to any text updates and updates the 12 | * database and {@link Document} appropriately. 13 | * 14 | * @param document The {@link Document} to add 15 | */ 16 | void addTab(Document document); 17 | 18 | /** 19 | * Removes the associated {@link EmojiComponent}'s {@link Document} from the database, and the tab controller, 20 | * switching tabs if necessary. 21 | * 22 | * @param tab The tab to remove 23 | */ 24 | void removeTab(Tab tab); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/ImageConverter.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord; 2 | 3 | import javax.imageio.ImageIO; 4 | import java.awt.image.BufferedImage; 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | public class ImageConverter { 9 | 10 | public static void main(String[] args) throws IOException { 11 | separateImages(ImageIO.read(new File("welcome_text.png"))); 12 | } 13 | 14 | private static void separateImages(BufferedImage input) throws IOException { 15 | var iconSize = 256; 16 | 17 | var col = input.getWidth() / iconSize; 18 | var row = input.getHeight() / iconSize; 19 | 20 | for (int yBlock = 0; yBlock < row; yBlock++) { 21 | for (int xBlock = 0; xBlock < col; xBlock++) { 22 | BufferedImage subImage = input.getSubimage(xBlock * iconSize, yBlock * iconSize, iconSize, iconSize); 23 | ImageIO.write(subImage, "png", new File("welcome_emojis/" + xBlock + "w" + yBlock + ".png")); 24 | System.out.println(xBlock + "w" + yBlock); 25 | } 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/CommandHelp.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands; 2 | 3 | import com.uddernetworks.emojide.discord.commands.manager.EmbedUtils; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | import com.uddernetworks.emojide.web.WebCallbackHandler; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.entities.TextChannel; 8 | 9 | import java.util.Map; 10 | 11 | import static com.uddernetworks.emojide.main.EmojIDE.ZWS; 12 | 13 | public class CommandHelp { 14 | 15 | private static WebCallbackHandler callbackHandler; 16 | 17 | public static void initHelp(EmojIDE emojIDE) { 18 | callbackHandler = emojIDE.getWebCallbackHandler(); 19 | callbackHandler.registerCommandCallback("help", (member, channel, query) -> send(member, channel)); 20 | } 21 | 22 | public static void send(Member member, TextChannel channel) { 23 | var emptyQuery = Map.of("member", member.getId(), "channel", channel.getId()); 24 | EmbedUtils.sendEmbed(channel, member, "EmojIDE Command Help", embed -> embed.setDescription("Help for the EmojIDE commands (**base** is just the base command, and no arguments)") 25 | .addField("!help", 26 | commandRow(callbackHandler.generateMdLink("base", "help", emptyQuery), "Show this help menu"), false) 27 | .addField("!purge", 28 | commandRow(callbackHandler.generateMdLink("base", "purge", emptyQuery), "Purges all messages by the bot in the current channel") + 29 | commandRow("user [user mention]", "Purges all messages by a user in the current channel"), false) 30 | .addField("!ide", 31 | commandRow(callbackHandler.generateMdLink("setchannel", "setchannel", emptyQuery), "Sets the current channel to the channel displaying the IDE") + 32 | commandRow(callbackHandler.generateMdLink("fonts", "fonts", emptyQuery), "Lists available fonts with the option to select one") + 33 | commandRow("setfont [font]", "Sets the font with the given name from `!ide fonts`") + 34 | commandRow(callbackHandler.generateMdLink("themes", "themes", emptyQuery), "Lists available themes with the option to select one") + 35 | commandRow("settheme [theme]", "Sets the theme with the given name from `!ide themes`"), false) 36 | .addField(String.valueOf(ZWS), 37 | commandRow(callbackHandler.generateMdLink("start", "start", emptyQuery), "Starts the IDE without clearing any past messages") + 38 | commandRow(callbackHandler.generateMdLink("stop", "stop", emptyQuery), "Stops the IDE and removes its messages") + 39 | commandRow(callbackHandler.generateMdLink("restart", "restart", emptyQuery), "Restart the IDE by stopping and starting it again"), false) 40 | .addField("!emoji", 41 | commandRow(callbackHandler.generateMdLink("info", "info", emptyQuery), "Shows general information on the emojis and their servers") + 42 | commandRow("inspect [emoji name]", "Inspects an emoji by its name") + 43 | commandRow("cinspect [character]", "Shows all emojis for the given character") + 44 | commandRow("rem [emoji name]", "Removes a single emoji that has been added by EmojIDE") + 45 | commandRow("grem [group name]", "Removes all emojis in a group seen by `!emoji info`") + 46 | commandRow("crem [character]", "Removes all colors (And fonts) of a given character"), false)); 47 | } 48 | 49 | private static String commandRow(String name, String description) { 50 | return (" **" + name + "**" + " ".repeat(7)).replace(" ", ZWS + " ") + " - " + description + "\n"; 51 | } 52 | 53 | public static String space(int amount) { 54 | return (ZWS + " ").repeat(amount); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands; 2 | 3 | import com.uddernetworks.emojide.discord.commands.manager.Argument; 4 | import com.uddernetworks.emojide.discord.commands.manager.ArgumentError; 5 | import com.uddernetworks.emojide.discord.commands.manager.Command; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.entities.TextChannel; 8 | 9 | @Command(name = "help", aliases = "h", minArgs = 0, maxArgs = 0) 10 | public class HelpCommand { 11 | @Argument() 12 | public void base(Member member, TextChannel channel) { 13 | CommandHelp.send(member, channel); 14 | } 15 | 16 | @ArgumentError 17 | public void error(Member member, TextChannel channel, String command) { 18 | CommandHelp.send(member, channel); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/PurgeCommand.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands; 2 | 3 | import com.uddernetworks.emojide.discord.commands.manager.*; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | import net.dv8tion.jda.api.Permission; 6 | import net.dv8tion.jda.api.entities.Member; 7 | import net.dv8tion.jda.api.entities.TextChannel; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.regex.Pattern; 12 | import java.util.stream.Collectors; 13 | 14 | @Command(name = "purge", minArgs = 0, maxArgs = 2, permission = Permission.ADMINISTRATOR) 15 | public class PurgeCommand { 16 | 17 | private static Logger LOGGER = LoggerFactory.getLogger(PurgeCommand.class); 18 | 19 | private EmojIDE emojIDE; 20 | private Pattern idPattern = Pattern.compile("[0-9]{18}"); 21 | 22 | public PurgeCommand(EmojIDE emojIDE) { 23 | this.emojIDE = emojIDE; 24 | 25 | var callbackHandler = emojIDE.getWebCallbackHandler(); 26 | callbackHandler.registerCommandCallback("purge", (member, channel, query) -> purge(channel)); 27 | } 28 | 29 | @Argument() 30 | public void base(Member member, TextChannel channel) { 31 | purge(channel); 32 | } 33 | 34 | @Argument(format = "user *") 35 | public void user(Member member, TextChannel channel, ArgumentList args) { 36 | var userTag = args.nextArg().getString(); 37 | Member user = null; 38 | var matcher = idPattern.matcher(userTag); 39 | if (matcher.find()) { 40 | var found = matcher.group(); 41 | user = channel.getGuild().getMemberById(found); 42 | } 43 | 44 | if (user == null) { 45 | EmbedUtils.error(channel, member, "Couldn't find user `" + userTag + "`"); 46 | return; 47 | } 48 | 49 | var userId = user.getIdLong(); 50 | channel.purgeMessages(channel.getIterableHistory().stream().filter(message -> message.getAuthor().getIdLong() == userId).collect(Collectors.toList())); 51 | } 52 | 53 | @ArgumentError 54 | public void error(Member member, TextChannel channel, String command) { 55 | EmbedUtils.error(channel, member, "Error " + command + "\nTry doing `!emoji` for help"); 56 | } 57 | 58 | private void purge(TextChannel textChannel) { 59 | var selfId = emojIDE.getJda().getSelfUser().getIdLong(); 60 | textChannel.purgeMessages(textChannel.getIterableHistory().stream().filter(message -> message.getAuthor().getIdLong() == selfId).collect(Collectors.toList())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/choosable/ChoosingList.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.choosable; 2 | 3 | import com.uddernetworks.emojide.main.ChoosableEnum; 4 | import net.dv8tion.jda.api.entities.Member; 5 | import net.dv8tion.jda.api.entities.TextChannel; 6 | 7 | import java.util.function.Consumer; 8 | import java.util.function.Function; 9 | 10 | /** 11 | * A radio list of enums that may be selected via emojis. This is the same list that appears from the command 12 | * !ide fonts and others. The examples used in the class will derive from the following embed screenshot: 13 | *

14 | * The '!ide fonts' embed 15 | * 16 | * @param The enum to choose 17 | */ 18 | public interface ChoosingList { 19 | 20 | /** 21 | * The unique name of the emoji, as short as possible is usually ideal (Due to embed limits). This is currently 22 | * just the {@link Object#hashCode()} of the class, but may need to just be ain incremental value for 23 | * conciseness. 24 | * 25 | * @return The unique name 26 | */ 27 | String getName(); 28 | 29 | /** 30 | * Initially sends a choosing embed to the given channel. 31 | * 32 | * @param member The requester of the embed 33 | * @param channel The channel to send the embed to 34 | * @param title The title of the embed, e.g. Fonts 35 | * @param description The description of the embed, e.g. The following are the fonts used by the IDE. Either 36 | * click the :uns: to apply the font, or type `!ide setfont "name"` 37 | */ 38 | void sendEmbed(Member member, TextChannel channel, String title, String description); 39 | 40 | /** 41 | * Invoked by the {@link ChoosingListManager} when an enum is selected. 42 | * 43 | * @param member The requester of the interacted message 44 | * @param channel The channel in which the interacted message is in 45 | * @param messageId The Discord ID of the message being interacted with 46 | * @param ordinal The ordinal of the enum (Not yet validated) 47 | */ 48 | void activateEnum(Member member, TextChannel channel, long messageId, int ordinal); 49 | 50 | /** 51 | * This is not specific towards the list portion of the chooser, but rather the manual selection. This makes 52 | * the manual choosing of an Enum just a single line in the command registration. The following screenshots will be 53 | * used as reference in the method's docs: 54 | *

55 | * The success embed of `!ide setfont [name]` 56 | *

57 | * The failure embed of `!ide setfont [name]` 58 | * 59 | * @param channel The channel to send the embed 60 | * @param member The requester of the embed 61 | * @param enumName The argument of the command, set by the end user. This has no validation before this method 62 | * @param lowercaseDescriptor The lowercase description word of the enum shown in failures, e.g. font 63 | * @param onSelect The action to perform when a given Enum is selected 64 | * @param successTitle The title to display when an item was successfully chosen, e.g. Changed Font 65 | * @param successDescription The description (Supplied by the chosen Enum) to display when an item was successfully 66 | * chosen, e.g. Changed active font to Fira Code 67 | */ 68 | void manualChoose(TextChannel channel, Member member, String enumName, String lowercaseDescriptor, Consumer onSelect, String successTitle, Function successDescription); 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/choosable/ChoosingListManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.choosable; 2 | 3 | import com.uddernetworks.emojide.main.ChoosableEnum; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.function.Consumer; 9 | import java.util.function.Supplier; 10 | 11 | public interface ChoosingListManager { 12 | 13 | /** 14 | * Creates a {@link ChoosingList} with the given parameters. 15 | * 16 | * @param emojIDE The {@link EmojIDE} instance 17 | * @param active The supplier to get the active Enum 18 | * @param onSelect The consumer to be invoked when a new item is selected 19 | * @param choosableEnum The class of the Enum 20 | * @param The {@link ChoosableEnum} Enum being selected 21 | * @return The {@link ChoosableEnum} created 22 | */ 23 | ChoosingList createChoosingList(EmojIDE emojIDE, Supplier active, Consumer onSelect, Class choosableEnum); 24 | 25 | /** 26 | * Gets a {@link ChoosingList} by its unique name, if existant. 27 | * 28 | * @param name The unique name of the {@link ChoosingList} 29 | * @return The {@link ChoosingList} if existent 30 | */ 31 | Optional getChoosingListByName(String name); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/choosable/DefaultChoosingList.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.choosable; 2 | 3 | import com.uddernetworks.emojide.discord.commands.manager.EmbedUtils; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 6 | import com.uddernetworks.emojide.discord.font.Font; 7 | import com.uddernetworks.emojide.main.ChoosableEnum; 8 | import com.uddernetworks.emojide.main.EmojIDE; 9 | import net.dv8tion.jda.api.EmbedBuilder; 10 | import net.dv8tion.jda.api.entities.Member; 11 | import net.dv8tion.jda.api.entities.Message; 12 | import net.dv8tion.jda.api.entities.MessageEmbed; 13 | import net.dv8tion.jda.api.entities.TextChannel; 14 | 15 | import java.util.Arrays; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.function.Consumer; 19 | import java.util.function.Function; 20 | import java.util.function.Supplier; 21 | import java.util.stream.Collectors; 22 | 23 | import static com.uddernetworks.emojide.discord.commands.CommandHelp.space; 24 | 25 | public class DefaultChoosingList implements ChoosingList { 26 | 27 | private EmojIDE emojIDE; 28 | private EmojiManager emojiManager; 29 | private Supplier active; 30 | private Consumer onSelect; 31 | private Class choosableEnum; 32 | 33 | private String name; 34 | 35 | DefaultChoosingList(EmojIDE emojIDE, Supplier active, Consumer onSelect, Class choosableEnum) { 36 | this.emojIDE = emojIDE; 37 | this.emojiManager = emojIDE.getEmojiManager(); 38 | this.active = active; 39 | this.onSelect = onSelect; 40 | this.choosableEnum = choosableEnum; 41 | this.name = String.valueOf(choosableEnum.hashCode()); 42 | } 43 | 44 | @Override 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | @Override 50 | public void sendEmbed(Member member, TextChannel channel, String title, String description) { 51 | var emptyQuery = Map.of("member", member.getId(), "channel", channel.getId()); 52 | var editing = EmbedUtils.sendEmbed(channel, member, title, embed -> embed.setDescription(description)); 53 | editing.editMessage(createListEmbed(editing, emptyQuery)).queue(); 54 | } 55 | 56 | @Override 57 | public void activateEnum(Member member, TextChannel channel, long messageId, int ordinal) { 58 | if (ordinal >= choosableEnum.getEnumConstants().length) return; 59 | var emptyQuery = Map.of("member", member.getId(), "channel", channel.getId()); 60 | 61 | onSelect.accept(choosableEnum.getEnumConstants()[ordinal]); 62 | channel.retrieveMessageById(messageId).submit().thenAccept(message -> { 63 | if (message == null || message.getEmbeds().size() != 1 || message.getAuthor().getIdLong() != emojIDE.getJda().getSelfUser().getIdLong()) return; 64 | message.editMessage(createListEmbed(message, emptyQuery)).queue(); 65 | }); 66 | } 67 | 68 | @Override 69 | public void manualChoose(TextChannel channel, Member member, String enumName, String lowercaseDescriptor, Consumer onSelect, String successTitle, Function successDescription) { 70 | var fontOptional = Arrays.stream(choosableEnum.getEnumConstants()).filter(enu -> enu.getName().equalsIgnoreCase(enumName)).findFirst(); 71 | if (fontOptional.isEmpty()) { 72 | EmbedUtils.error(channel, member, "Unknown " + lowercaseDescriptor + " `" + enumName + "`"); 73 | return; 74 | } 75 | 76 | var selected = fontOptional.get(); 77 | onSelect.accept(selected); 78 | EmbedUtils.sendEmbed(channel, member, successTitle, successDescription.apply(selected)); 79 | } 80 | 81 | private MessageEmbed createListEmbed(Message message, Map emptyQuery) { 82 | var editingEmbed = new EmbedBuilder(message.getEmbeds().get(0)); 83 | var webCallbackHandler = emojIDE.getWebCallbackHandler(); 84 | editingEmbed.clearFields(); 85 | 86 | var paddedLength = Arrays.stream(Font.values()).mapToInt(font -> font.getName().length()).max().orElse(0) + 2; 87 | 88 | Arrays.stream(choosableEnum.getEnumConstants()).forEach(enu -> { 89 | boolean thisActive = active.get() == enu; 90 | var usingFont = enu instanceof Font ? (Font) enu : emojIDE.getFontManager().getActive(); 91 | var emojiName = paddedEmoji(enu.getName().chars().mapToObj(cha -> emojiManager.getTextEmoji((char) cha, usingFont).getDisplay()).collect(Collectors.joining(" ")), paddedLength); 92 | 93 | var query = new HashMap<>(emptyQuery); 94 | query.put("id", getName()); 95 | query.put("ordinal", String.valueOf(enu.ordinal())); 96 | query.put("originating", message.getId()); 97 | editingEmbed.addField(enu.getName(), emojiName + space(4) + 98 | (thisActive ? StaticEmoji.SELECT.getDisplay() : webCallbackHandler.generateMdLink(StaticEmoji.UNSELECTED.getDisplay(), "apply", query)), true); 99 | }); 100 | 101 | return editingEmbed.build(); 102 | } 103 | 104 | private String paddedEmoji(String input, int length) { 105 | return input.replaceAll("\\s+", "") + StaticEmoji.TRANSPARENT.getDisplay().repeat(Math.max(0, length - input.split("\\s+").length)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/choosable/DefaultChoosingListManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.choosable; 2 | 3 | import com.uddernetworks.emojide.discord.font.Font; 4 | import com.uddernetworks.emojide.main.ChoosableEnum; 5 | import com.uddernetworks.emojide.main.EmojIDE; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.function.Consumer; 13 | import java.util.function.Supplier; 14 | 15 | public class DefaultChoosingListManager implements ChoosingListManager { 16 | 17 | private EmojIDE emojIDE; 18 | private List choosingLists = new ArrayList<>(); 19 | 20 | public DefaultChoosingListManager(EmojIDE emojIDE) { 21 | this.emojIDE = emojIDE; 22 | 23 | emojIDE.getWebCallbackHandler().registerCommandCallback("apply", Arrays.asList("id", "ordinal", "originating"), (member, channel, query) -> { 24 | var currentListOptional = getChoosingListByName(query.get("id")); 25 | if (currentListOptional.isEmpty()) return; 26 | 27 | var activatingString = query.get("ordinal"); 28 | int activating; 29 | if (!StringUtils.isNumeric(activatingString) || (activating = Integer.parseInt(activatingString)) < 0 || activating >= Font.values().length) return; 30 | 31 | var originatingString = query.get("originating"); 32 | if (!StringUtils.isNumeric(originatingString)) return; 33 | currentListOptional.get().activateEnum(member, channel, Long.parseLong(originatingString), activating); 34 | }); 35 | } 36 | 37 | @Override 38 | public ChoosingList createChoosingList(EmojIDE emojIDE, Supplier active, Consumer onSelect, Class choosableEnum) { 39 | var choosingList = new DefaultChoosingList(emojIDE, active, onSelect, choosableEnum); 40 | choosingLists.add(choosingList); 41 | return choosingList; 42 | } 43 | 44 | @Override 45 | public Optional getChoosingListByName(String name) { 46 | return choosingLists.stream().filter(choosingList -> choosingList.getName().equals(name)).findFirst(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/Argument.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | import net.dv8tion.jda.api.Permission; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.METHOD}) 12 | public @interface Argument { 13 | String format() default ""; 14 | Permission[] permission() default {}; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/ArgumentError.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD}) 11 | public @interface ArgumentError { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/ArgumentList.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class ArgumentList { 8 | 9 | private List args = new ArrayList<>(); 10 | private int currentIndex = 0; 11 | 12 | public ArgumentList() {} 13 | 14 | public ArgumentList(CommandArg... args) { 15 | this.args.addAll(Arrays.asList(args)); 16 | } 17 | 18 | public void add(CommandArg commandArg) { 19 | args.add(commandArg); 20 | } 21 | 22 | public CommandArg nextArg() { 23 | currentIndex++; 24 | return args.get(currentIndex - 1); 25 | } 26 | 27 | public boolean isEmpty() { 28 | return args.isEmpty(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/Command.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | import net.dv8tion.jda.api.Permission; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.TYPE}) 12 | public @interface Command { 13 | String name(); 14 | String[] aliases() default ""; 15 | Permission[] permission() default {}; 16 | int minArgs() default -1; 17 | int maxArgs() default -1; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/CommandArg.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | public class CommandArg { 4 | 5 | private String input; 6 | 7 | public CommandArg(String input) { 8 | this.input = input; 9 | } 10 | 11 | public String getString() { 12 | return input; 13 | } 14 | 15 | public int getInt() { 16 | return Integer.parseInt(input); 17 | } 18 | 19 | public boolean getBoolean() { 20 | return Boolean.parseBoolean(input); 21 | } 22 | 23 | public long getLong() { 24 | return Long.parseLong(input); 25 | } 26 | 27 | public short getShort() { 28 | return Short.parseShort(input); 29 | } 30 | 31 | public byte getByte() { 32 | return Byte.parseByte(input); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/CommandResult.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | public enum CommandResult { 4 | SUCCESS, 5 | INVALID_SYNTAX, 6 | INVALID_PERMISSION; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/commands/manager/EmbedUtils.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.commands.manager; 2 | 3 | import net.dv8tion.jda.api.EmbedBuilder; 4 | import net.dv8tion.jda.api.entities.Member; 5 | import net.dv8tion.jda.api.entities.Message; 6 | import net.dv8tion.jda.api.entities.MessageEmbed; 7 | import net.dv8tion.jda.api.entities.TextChannel; 8 | import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent; 9 | import net.dv8tion.jda.api.hooks.ListenerAdapter; 10 | 11 | import javax.annotation.Nonnull; 12 | import java.awt.*; 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.function.Consumer; 17 | 18 | public class EmbedUtils extends ListenerAdapter { 19 | 20 | private static Map messageRequesters = Collections.synchronizedMap(new HashMap<>()); 21 | 22 | public static void error(TextChannel channel, Member author, String message) { 23 | EmbedBuilder eb = new EmbedBuilder(); 24 | 25 | eb.setTitle("Dammit", null); 26 | eb.setColor(new Color(0xFF0000)); 27 | 28 | eb.setDescription(message); 29 | 30 | eb.setFooter("In response to " + author.getEffectiveName(), author.getUser().getAvatarUrl()); 31 | channel.sendMessage(eb.build()).submit().thenAccept(sent -> { 32 | sent.addReaction("U+1F5D1").queue(); 33 | messageRequesters.put(sent, author.getIdLong()); 34 | }); 35 | } 36 | 37 | public static Message sendEmbed(TextChannel channel, Member author, String title, String description) { 38 | return sendEmbed(channel, author, title, embed -> embed.setDescription(description)); 39 | } 40 | 41 | public static Message sendEmbed(TextChannel channel, Member author, String title, Consumer embedBuilderConsumer) { 42 | var message = channel.sendMessage(createEmbed(author, title, embedBuilderConsumer)).complete(); 43 | message.addReaction("U+1F5D1").queue(); 44 | messageRequesters.put(message, author.getIdLong()); 45 | return message; 46 | } 47 | 48 | public static MessageEmbed createEmbed(Member author, String title, Consumer embedBuilderConsumer) { 49 | EmbedBuilder eb = new EmbedBuilder(); 50 | 51 | eb.setTitle(title, null); 52 | eb.setColor(new Color(0x424BE9)); 53 | 54 | eb.setFooter("Requested by " + author.getEffectiveName(), author.getUser().getAvatarUrl()); 55 | 56 | embedBuilderConsumer.accept(eb); 57 | return eb.build(); 58 | } 59 | 60 | @Override 61 | public void onGuildMessageReactionAdd(@Nonnull GuildMessageReactionAddEvent event) { 62 | if (event.getUser().isBot()) return; 63 | if (!event.getReactionEmote().getAsCodepoints().equalsIgnoreCase("U+1F5D1")) return; 64 | event.getChannel().retrieveMessageById(event.getMessageIdLong()).submit().thenAccept(message -> { 65 | var userId = event.getUser().getIdLong(); 66 | 67 | if (messageRequesters.containsKey(message) && messageRequesters.get(message) != userId) { 68 | if (userId != 249962392241307649L) return; 69 | } 70 | 71 | if (message.getAuthor().getIdLong() == event.getJDA().getSelfUser().getIdLong()) { 72 | message.delete().queue(); 73 | } 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/emoji/Emoji.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.emoji; 2 | 3 | /** 4 | * This interface allows for custom Emojis, i.e. dynamically created ones, originally intended for uploaded and 5 | * converted images of arbitrary sizes. 6 | */ 7 | public interface Emoji { 8 | 9 | /** 10 | * Gets the name of the emoji. 11 | * 12 | * @return The name of the emoji 13 | */ 14 | String getName(); 15 | 16 | /** 17 | * Gets the text to display in messages to show the emoji. 18 | * 19 | * @return The displaying text of the emoji 20 | */ 21 | String getDisplay(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/emoji/EmojiManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.emoji; 2 | 3 | import com.uddernetworks.emojide.discord.font.Font; 4 | import net.dv8tion.jda.api.entities.Guild; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * The manager for all {@link Emoji}s. Upon initialization, any emojis from {@link StaticEmoji} are uploaded to an emoji 11 | * server in the config list, if the emoji is non existant anywhere. 12 | */ 13 | public interface EmojiManager { 14 | 15 | /** 16 | * Gets the {@link Emoji} by the given name, case insensitive. 17 | * 18 | * @param name The name of the emoji 19 | * @return The {@link Emoji} 20 | */ 21 | Emoji getEmoji(String name); 22 | 23 | /** 24 | * Gets the text emoji for the given character, in the currently applied font. 25 | * 26 | * @param character The character to get the emoji of 27 | * @return The emoji 28 | */ 29 | Emoji getTextEmoji(char character); 30 | 31 | /** 32 | * Gets the text emoji for the given character, in the currently applied font. This assumes the name argument 33 | * is the emoji name, i.e. o96 34 | * 35 | * @param name The character to get the emoji of 36 | * @return The emoji 37 | */ 38 | Emoji getTextEmoji(String name); 39 | 40 | /** 41 | * Gets the text emoji for the given character, in the currently applied typeface. 42 | * 43 | * @param character The character to get the emoji of 44 | * @param font The font to render in 45 | * @return The emoji 46 | */ 47 | Emoji getTextEmoji(char character, Font font); 48 | 49 | /** 50 | * Gets the text emoji for the given character, in the given font. This assumes the name argument 51 | * is the emoji name, i.e. o96 52 | * 53 | * @param name The character to get the emoji of 54 | * @param font The font to render in 55 | * @return The emoji 56 | */ 57 | Emoji getTextEmoji(String name, Font font); 58 | 59 | /** 60 | * Gets an immutable map of all custom emojis uploaded and used by EmojIDE. 61 | * 62 | * @return All emojis by EmojIDE 63 | */ 64 | Map getEmojis(); 65 | 66 | /** 67 | * Gets an immutable map of the amount of custom emojis by EmojIDE in each emoji server. 68 | * 69 | * @return The distribution of emojis among emoji servers 70 | */ 71 | Map getEmojiDistribution(); 72 | 73 | /** 74 | * Gets an immutable list of servers used solely for emoji uploading and fetching. 75 | * 76 | * @return The emoji servers 77 | */ 78 | List getEmojiServers(); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/font/DefaultFontManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.font; 2 | 3 | import com.electronwill.nightconfig.core.file.FileConfig; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | 6 | public class DefaultFontManager implements FontManager { 7 | 8 | private EmojIDE emojIDE; 9 | private FileConfig config; 10 | private Font active; 11 | 12 | public DefaultFontManager(EmojIDE emojIDE) { 13 | this.emojIDE = emojIDE; 14 | config = EmojIDE.getConfigManager().getConfig(); 15 | 16 | int fontOrdinal = config.getOrElse("ide.font", -1); 17 | if (fontOrdinal == -1) config.set("ide.font", fontOrdinal = 0); 18 | active = Font.values()[fontOrdinal]; 19 | } 20 | 21 | @Override 22 | public Font getActive() { 23 | return active; 24 | } 25 | 26 | @Override 27 | public void setActive(Font active) { 28 | config.set("ide.font", active.ordinal()); 29 | this.active = active; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/font/Font.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.font; 2 | 3 | import com.uddernetworks.emojide.main.ChoosableEnum; 4 | 5 | import java.util.Arrays; 6 | import java.util.Optional; 7 | 8 | public enum Font implements ChoosableEnum { 9 | CONSOLAS("", "Consolas"), 10 | FIRA_CODE("f", "Fira Code"); 11 | 12 | private String prefix; 13 | private String name; 14 | 15 | Font(String prefix, String name) { 16 | this.prefix = prefix; 17 | this.name = name; 18 | } 19 | 20 | public static Optional fromName(String name) { 21 | return Arrays.stream(values()).filter(font -> font.name.equalsIgnoreCase(name)).findFirst(); 22 | } 23 | 24 | public String getPrefix() { 25 | return prefix; 26 | } 27 | 28 | @Override 29 | public String getName() { 30 | return name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/discord/font/FontManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.discord.font; 2 | 3 | public interface FontManager { 4 | 5 | /** 6 | * Gets the active {@link Font} being used 7 | * 8 | * @return The active {@link Font} 9 | */ 10 | Font getActive(); 11 | 12 | /** 13 | * Sets the given {@link Font} to be active. 14 | * 15 | * @param active The {@link Font} to be active 16 | */ 17 | void setActive(Font active); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/Cancellable.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | public abstract class Cancellable extends Event { 4 | 5 | private boolean cancel; 6 | 7 | /** 8 | * If the event has been cancelled, it will go through no other handlers. 9 | * 10 | * @return If the event has been cancelled 11 | */ 12 | public boolean isCancelled() { 13 | return cancel; 14 | } 15 | 16 | /** 17 | * Sets if the event should be cancelled or not, and if no other event handlers should receive it. 18 | * 19 | * @param cancel If the event is cancelled 20 | */ 21 | public void setCancelled(boolean cancel) { 22 | this.cancel = cancel; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/Event.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | public abstract class Event { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/EventRaiser.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class EventRaiser { 7 | 8 | private static List raisables = new ArrayList<>(); 9 | 10 | /** 11 | * Adds the given {@link Raisable} instance to the internal list to be used in the future. 12 | * 13 | * @param raisable The {@link Raisable} to add 14 | */ 15 | public static void createEvent(Raisable raisable) { 16 | raisables.add(raisable); 17 | } 18 | 19 | /** 20 | * Raises the given {@link Event} to invoke all listeners. 21 | * 22 | * @param clazz The class of the {@link Raisable} added 23 | * @param event The {@link Event} to raise 24 | * @param The {@link Event} type being risen 25 | */ 26 | public static void raiseEvent(Class clazz, T event) { 27 | getRaisable(clazz).raise(event); 28 | } 29 | 30 | /** 31 | * Raises the given {@link Event} to invoke all listeners. 32 | * 33 | * @param name The name of the {@link Raisable} added 34 | * @param event The {@link Event} to raise 35 | * @param The {@link Event} type being risen 36 | */ 37 | public static void raiseEvent(String name, T event) { 38 | getRaisable(name).raise(event); 39 | } 40 | 41 | /** 42 | * Gets a {@link Raisable} by the given name, throwing if not found. 43 | * 44 | * @param clazz The class ofd the {@link Raisable} 45 | * @return The found {@link Raisable} 46 | */ 47 | public static Raisable getRaisable(Class clazz) { 48 | return raisables.stream().filter(raisable -> raisable.getClass().equals(clazz)).findFirst().orElseThrow(); 49 | } 50 | 51 | /** 52 | * Gets a {@link Raisable} by the given name, throwing if not found. 53 | * 54 | * @param name The {@link Raisable} name 55 | * @return The found {@link Raisable} 56 | */ 57 | public static Raisable getRaisable(String name) { 58 | return raisables.stream().filter(raisable -> raisable.getName().equalsIgnoreCase(name)).findFirst().orElseThrow(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/Handler.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Handler { 11 | String event(); 12 | boolean overrideSuspend() default false; 13 | 14 | /** 15 | * The higher the priority, the sooner the event will be received. 16 | * 17 | * @return The priority 18 | */ 19 | Priority priority() default Priority.NORMAL; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/Priority.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | public enum Priority { 4 | LUDICROUSLY_HIGH(6), 5 | SUPER_DUPER_HIGH(5), 6 | VERY_HIGH(4), 7 | HIGH(3), 8 | SLIGHTLY_HIGHER_THAN_NORMAL(2), 9 | NORMAL(1), 10 | LOW(0); 11 | 12 | private int priority; 13 | 14 | Priority(int priority) { 15 | this.priority = priority; 16 | } 17 | 18 | public int getPriority() { 19 | return priority; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/event/Raisable.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.event; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.Method; 7 | import java.util.*; 8 | 9 | public abstract class Raisable { 10 | 11 | private static Logger LOGGER = LoggerFactory.getLogger(Raisable.class); 12 | 13 | final String name; 14 | final Class type; 15 | Map> eventClasses = new HashMap<>(); 16 | Map> suspended = new HashMap<>(); 17 | 18 | public Raisable(String name, Class type) { 19 | this.name = name; 20 | this.type = type; 21 | } 22 | 23 | /** 24 | * Gets the identifying name of the {@link Raisable}. 25 | * 26 | * @return The name 27 | */ 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | /** 33 | * Gets the instance of {@link Event} class that will be listened to. 34 | * 35 | * @return The event type 36 | */ 37 | public Class getType() { 38 | return type; 39 | } 40 | 41 | /** 42 | * Adds an object to listen to events from the current {@link Raisable}. 43 | * 44 | * @param listener The class to listen 45 | * @return The current {@link Raisable} 46 | */ 47 | public Raisable addListener(Object listener) { 48 | Class current = listener.getClass(); 49 | registerListener(listener, current); 50 | while (current.getSuperclass() != null) { 51 | if (Object.class.equals(current = current.getSuperclass())) break; 52 | registerListener(listener, current); 53 | } 54 | return this; 55 | } 56 | 57 | private void registerListener(Object listener, Class clazz) { 58 | Arrays.stream(clazz.getDeclaredMethods()).forEach(method -> { 59 | if (method.getParameterCount() != 1) return; 60 | var type = method.getParameters()[0].getType(); 61 | if (method.getParameterCount() == 1 && type.equals(this.type)) { 62 | method.setAccessible(true); 63 | 64 | var handler = method.getAnnotation(Handler.class); 65 | if (handler == null || !handler.event().equalsIgnoreCase(name)) return; 66 | 67 | eventClasses.computeIfAbsent(listener, i -> new ArrayList<>()).add(method); 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * Removes the given listening object instance from listening to events. 74 | * 75 | * @param listener The class to remove 76 | * @return The current {@link Raisable} 77 | */ 78 | public Raisable removeListener(Object listener) { 79 | eventClasses.remove(listener); 80 | return this; 81 | } 82 | 83 | /** 84 | * Raises the given {@link Event} 85 | * 86 | * @param event The {@link Event} to raise 87 | */ 88 | public void raise(T event) { 89 | var cancellable = event instanceof Cancellable ? (Cancellable) event : null; 90 | var allMethods = new ArrayList>(); 91 | new HashMap<>(this.eventClasses).forEach((object, methods) -> methods.forEach(method -> allMethods.add(new AbstractMap.SimpleEntry<>(object, method)))); 92 | allMethods.sort(Comparator.comparingInt(entry -> entry.getValue().getAnnotation(Handler.class).priority().getPriority())); 93 | Collections.reverse(allMethods); 94 | 95 | for (Map.Entry handler : allMethods) { 96 | var object = handler.getKey(); 97 | var method = handler.getValue(); 98 | try { 99 | method.invoke(object, event); 100 | } catch (ReflectiveOperationException | IllegalArgumentException e) { 101 | LOGGER.error("Error while invoking event on " + object.getClass().getCanonicalName() + "#" + method.getName(), e); 102 | } 103 | if (cancellable != null && cancellable.isCancelled()) { 104 | break; 105 | } 106 | } 107 | 108 | new HashMap<>(this.eventClasses).keySet() 109 | .stream() 110 | .filter(object -> object instanceof Class) 111 | .forEach(eventClasses::remove); 112 | } 113 | 114 | /** 115 | * Saves and then disabled all listeners. They may be resumed via {@link #resumeListeners()}. 116 | */ 117 | public void suspendListeners() { 118 | if (!suspended.isEmpty()) return; 119 | eventClasses.forEach((object, methods) -> methods.stream().filter(method -> !method.getAnnotation(Handler.class).overrideSuspend()).forEach(method -> suspended.put(object, methods))); 120 | 121 | eventClasses.forEach((object, methods) -> methods.removeAll(suspended.getOrDefault(object, Collections.emptyList()))); 122 | LOGGER.info("All keyboard listeners have been suspended"); 123 | } 124 | 125 | /** 126 | * Enabled all listeners disabled with {@link #suspendListeners()}. 127 | */ 128 | public void resumeListeners() { 129 | suspended.forEach((object, methods) -> methods.forEach(method -> eventClasses.computeIfAbsent(object, i -> new ArrayList<>()).add(method))); 130 | suspended.clear(); 131 | LOGGER.info("All keyboard listeners have been reset back to the last suspend"); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/generator/EmojiGenerator.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.generator; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 4 | 5 | /** 6 | * A generator to create emoji images for {@link StaticEmoji}s. 7 | */ 8 | public interface EmojiGenerator { 9 | 10 | /** 11 | * Creates letters in Consolas in the path `generated_emojis`. Solidly colored emojis are also created. 12 | */ 13 | void generate(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/CustomRenderedContainerFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 5 | import com.uddernetworks.emojide.gui.components.Displayer; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.function.Consumer; 10 | 11 | public class CustomRenderedContainerFrame extends DefaultEmojiContainer { 12 | 13 | private List> renderers = new ArrayList<>(); 14 | 15 | public CustomRenderedContainerFrame(Displayer displayer, int width, int height) { 16 | super(displayer, width, height); 17 | } 18 | 19 | @Override 20 | public Emoji[][] render(Emoji[][] initial) { 21 | var initiala = new Emoji[][][] {super.render(initial)}; 22 | renderers.forEach(t -> t.accept(initiala[0])); 23 | return initiala[0]; 24 | } 25 | 26 | public CustomRenderedContainerFrame addRenderer(Consumer renderer) { 27 | renderers.add(renderer); 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/EmptyContainerFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.gui.components.Displayer; 4 | import com.uddernetworks.emojide.gui.components.styled.StyledEmojiContainer; 5 | 6 | public class EmptyContainerFrame extends StyledEmojiContainer { 7 | public EmptyContainerFrame(Displayer displayer, int width, int height) { 8 | super(displayer, width, height); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/HighlightedTextFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 5 | import com.uddernetworks.emojide.gui.components.Displayer; 6 | import com.uddernetworks.emojide.ide.LanguageHighlighter; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class HighlightedTextFrame extends EditableDynamicTextFrame { 11 | 12 | private static Logger LOGGER = LoggerFactory.getLogger(HighlightedTextFrame.class); 13 | 14 | public HighlightedTextFrame(Displayer displayer, int width, int height, String text) { 15 | super(displayer, width, height); 16 | textBlock.setText(text); 17 | } 18 | 19 | @Override 20 | public Emoji[][] render(Emoji[][] initial) { 21 | initial = super.render(initial); 22 | 23 | var highlighter = new LanguageHighlighter(); 24 | drawHighlighted(initial, highlighter.highlightText(emojiManager, textBlock)); 25 | initial[cursorY - scrollY][Math.min(initial[0].length - 1, cursorX) - scrollX] = StaticEmoji.CURSOR; 26 | 27 | return initial; 28 | } 29 | 30 | private void drawHighlighted(Emoji[][] initial, Emoji[][] highlighted) { 31 | var insertX = 0; 32 | var insertY = 0; 33 | 34 | for (int y = 0; y < highlighted.length; y++) { 35 | System.arraycopy(highlighted[y], 0, initial[y + insertY], insertX, highlighted[0].length); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/StaticTextFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.Displayer; 5 | import com.uddernetworks.emojide.gui.components.styled.StyledEmojiComponent; 6 | import org.apache.commons.text.WordUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * A {@link StyledEmojiComponent} containing text that can be changed via {@link #setText(String)}. 12 | */ 13 | public class StaticTextFrame extends StyledEmojiComponent { 14 | 15 | private static Logger LOGGER = LoggerFactory.getLogger(StaticTextFrame.class); 16 | 17 | private String text; 18 | 19 | public StaticTextFrame(Displayer displayer, int width, int height) { 20 | this(displayer, width, height, ""); 21 | } 22 | 23 | public StaticTextFrame(Displayer displayer, int width, int height, String text) { 24 | super(displayer, width, height); 25 | this.text = text; 26 | } 27 | 28 | @Override 29 | public Emoji[][] render(Emoji[][] initial) { 30 | var wrapped = getWrapped(); 31 | var loopTo = Math.min(initial.length, wrapped.length); 32 | for (int y = 0; y < loopTo; y++) { 33 | var line = wrapped[y].toCharArray(); 34 | var emojiLine = initial[y]; 35 | for (int x = 0; x < line.length; x++) { 36 | emojiLine[x] = this.emojiManager.getTextEmoji(line[x]); 37 | } 38 | initial[y] = emojiLine; 39 | } 40 | return initial; 41 | } 42 | 43 | public void refresh() { 44 | clearCache(); 45 | displayer.update(); 46 | } 47 | 48 | private String[] getWrapped() { 49 | return WordUtils.wrap(this.text, this.width, "\n", true).split("\n"); 50 | } 51 | 52 | public String getText() { 53 | return text; 54 | } 55 | 56 | public StaticTextFrame setText(String text) { 57 | this.text = text; 58 | return this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/TextPromptFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.event.Handler; 5 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 6 | import com.uddernetworks.emojide.gui.components.Displayer; 7 | import com.uddernetworks.emojide.keyboard.KeyPressEvent; 8 | import com.uddernetworks.emojide.keyboard.KeyboardInputManager; 9 | import com.uddernetworks.emojide.keyboard.KeyboardRaisable; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.function.Consumer; 14 | 15 | import static com.uddernetworks.emojide.gui.components.styled.StyleUtils.drawThinBorder; 16 | 17 | public class TextPromptFrame extends DefaultEmojiContainer { 18 | 19 | private static Logger LOGGER = LoggerFactory.getLogger(TextPromptFrame.class); 20 | 21 | private KeyboardInputManager keyboardInputManager; 22 | private EditableStaticTextFrame textFrame; 23 | private Consumer enterText; 24 | 25 | public TextPromptFrame(Displayer displayer, String prompt) { 26 | this(displayer, 14, 4, prompt); 27 | } 28 | 29 | public TextPromptFrame(Displayer displayer, int width, int height, String prompt) { 30 | super(displayer, width, height); 31 | addChild(new StaticTextFrame(displayer, 10, 1, prompt), 1, 1); 32 | addChild(this.textFrame = new EditableStaticTextFrame(displayer, 10, 1), 1, 2); 33 | this.keyboardInputManager = displayer.getEmojIDE().getKeyboardInputManager(); 34 | KeyboardRaisable.get().addListener(this); 35 | } 36 | 37 | @Override 38 | public Emoji[][] render(Emoji[][] initial) { 39 | return drawThinBorder(super.render(initial)); 40 | } 41 | 42 | @Handler(event = "keyboard") 43 | private void onKeyPress(KeyPressEvent event) { 44 | if (event.isAlphanumeric()) return; 45 | displayer.getEmojIDE().getKeyboardInputManager().getPair(event.getStaticEmoji()).filter(pair -> pair == KeyboardInputManager.Pair.ENTER).ifPresent(enter -> { 46 | KeyboardRaisable.get().resumeListeners(); 47 | var text = textFrame.getTextBlock().getText(); 48 | LOGGER.info("Received text: {}", text); 49 | if (enterText != null) enterText.accept(text); 50 | }); 51 | } 52 | 53 | public void onEnter(Consumer enterText) { 54 | this.enterText = enterText; 55 | } 56 | 57 | /** 58 | * VERY important for cleanup! This unregisteres all listeners for the current object ALONG with with the actual 59 | * text box component! 60 | */ 61 | public void deactivate() { 62 | KeyboardRaisable.get().removeListener(this); 63 | KeyboardRaisable.get().removeListener(textFrame); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/WelcomeFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 5 | import com.uddernetworks.emojide.gui.components.Displayer; 6 | 7 | public class WelcomeFrame extends DefaultEmojiContainer { 8 | 9 | /** 10 | * Welcome window is by default 20 11 | * 12 | * @param displayer The {@link Displayer} 13 | */ 14 | public WelcomeFrame(Displayer displayer) { 15 | super(displayer, 56, 15); 16 | } 17 | 18 | @Override 19 | public Emoji[][] render(Emoji[][] initial) { 20 | 21 | int offsetX = 4; 22 | int offsetY = 3; 23 | 24 | for (int x = 0; x < 8; x++) { 25 | for (int y = 0; y < 2; y++) { 26 | initial[y + offsetY][x + offsetX] = this.emojiManager.getEmoji(x + "w" + y); 27 | } 28 | } 29 | 30 | insertText("By RubbaBoy", 4, 3, initial); 31 | 32 | insertText("Keybinds:", 4, 6, initial); 33 | insertText("Alt + Arrows", 4, 7, initial); 34 | insertText("Switch Tabs", 18, 7, initial); 35 | insertText("Ctrl + R", 4, 8, initial); 36 | insertText("Run file", 18, 8, initial); 37 | insertText("Ctrl + N", 4, 9, initial); 38 | insertText("New file", 18, 9, initial); 39 | insertText("Ctrl + X", 4, 10, initial); 40 | insertText("Delete document", 18, 10, initial); 41 | insertText("Ctrl + H", 4, 11, initial); 42 | insertText("Help tab", 18, 11, initial); 43 | 44 | return initial; 45 | } 46 | 47 | private void insertText(String text, int x, int y, Emoji[][] grid) { 48 | final int[] offset = {x}; 49 | text.chars().forEach(i -> grid[y + 3][offset[0]++] = this.emojiManager.getTextEmoji(String.valueOf(i))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/CachedDisplayer.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 6 | import com.uddernetworks.emojide.gui.render.RenderEngine; 7 | import com.uddernetworks.emojide.main.EmojIDE; 8 | import net.dv8tion.jda.api.entities.Message; 9 | import net.dv8tion.jda.api.entities.TextChannel; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.IntStream; 16 | 17 | /** 18 | * Displays a single EmojiComponent across as many required messages 19 | */ 20 | public class CachedDisplayer implements Displayer { 21 | 22 | private static final int EMOJI_LENGTH = 18; 23 | private static final int MAX_MESSAGE_LENGTH = 2000; 24 | private static final int MAX_EMOJIS_PER_LINE = 111; 25 | 26 | private EmojIDE emojIDE; 27 | private EmojiComponent child; 28 | private boolean displaying; 29 | private List messages = new ArrayList<>(); 30 | private Emoji filler; 31 | private TextChannel channel; 32 | private boolean insertKeyboard; 33 | 34 | private List cachedLines = new ArrayList<>(); 35 | 36 | /** 37 | * Creates a {@link CachedDisplayer} in the given channel. 38 | * 39 | * @param emojIDE The {@link EmojIDE} instance 40 | * @param channel The {@link TextChannel} to place everything in 41 | */ 42 | public CachedDisplayer(EmojIDE emojIDE, TextChannel channel) { 43 | this(emojIDE, channel, false); 44 | } 45 | 46 | /** 47 | * Creates a {@link CachedDisplayer} in the given channel. If insertKeyboard is true, a keyboard will be set after the 48 | * first update invocation. 49 | * 50 | * @param emojIDE The {@link EmojIDE} instance 51 | * @param channel The {@link TextChannel} to place everything in 52 | * @param insertKeyboard If a keyboard should be set afterwards 53 | */ 54 | public CachedDisplayer(EmojIDE emojIDE, TextChannel channel, boolean insertKeyboard) { 55 | this.emojIDE = emojIDE; 56 | this.filler = StaticEmoji.TRANSPARENT; 57 | this.channel = channel; 58 | this.insertKeyboard = insertKeyboard; 59 | 60 | RenderEngine.start(); 61 | } 62 | 63 | @Override 64 | public void setChild(EmojiComponent component) { 65 | setChild(component, false); 66 | } 67 | 68 | @Override 69 | public void setChild(EmojiComponent component, boolean autoUpdate) { 70 | if (this.child != null) throw new RuntimeException("Child has already been set for this Displayer!"); 71 | this.child = component; 72 | if (autoUpdate) update(); 73 | } 74 | 75 | @Override 76 | public EmojiComponent getChild() { 77 | return child; 78 | } 79 | 80 | @Override 81 | public boolean isDisplaying() { 82 | return displaying; 83 | } 84 | 85 | @Override 86 | public void update() { 87 | displaying = true; 88 | boolean sendMessages = this.messages.isEmpty(); 89 | if (sendMessages && this.child.getWidth() > MAX_EMOJIS_PER_LINE) 90 | throw new InvalidComponentException("Invalid width of " + this.child.getWidth() + " (Max is " + MAX_EMOJIS_PER_LINE + ")"); 91 | var emotes = this.child.getCachedRender(); 92 | if (emotes[0].length != this.child.getWidth() || emotes.length != this.child.getHeight()) 93 | throw new InvalidComponentException("Incorrect render dimensions received. Got " + emotes[0].length + "x" + emotes.length + ", expected " + this.child.getWidth() + "x" + this.child.getHeight()); 94 | if (cachedLines.isEmpty()) 95 | this.cachedLines = IntStream.range(0, emotes.length).mapToObj(i -> "").collect(Collectors.toList()); 96 | for (int i = 0; i < emotes.length; i++) { 97 | var line = emotes[i]; 98 | var message = Arrays.stream(line) 99 | .map(emote -> emote == null ? this.filler : emote) 100 | .map(Emoji::getDisplay) 101 | .collect(Collectors.joining()); 102 | if (i == 0) message = "\u200b\n\u200b\n\u200b\n" + message; 103 | if (sendMessages) { 104 | RenderEngine.queueSend(this.channel, message, completed -> this.messages.add(completed)); 105 | } else if (!this.cachedLines.get(i).equals(message)) { 106 | RenderEngine.queueEdit(this.messages.get(i), message, ignored -> {}); 107 | } 108 | this.cachedLines.set(i, message); 109 | } 110 | 111 | if (sendMessages && insertKeyboard) { 112 | this.emojIDE.getKeyboardInputManager().createKeyboard(channel); 113 | } 114 | } 115 | 116 | @Override 117 | public void stop(TextChannel channel) { 118 | channel.purgeMessages(this.messages); 119 | this.messages.clear(); 120 | } 121 | 122 | @Override 123 | public EmojIDE getEmojIDE() { 124 | return emojIDE; 125 | } 126 | 127 | @Override 128 | public EmojiManager getEmojiManager() { 129 | return this.emojIDE.getEmojiManager(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/ComponentUtils.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 5 | 6 | import java.util.Arrays; 7 | 8 | /** 9 | * Utilities used by {@link EmojiComponent} instances. 10 | */ 11 | public class ComponentUtils { 12 | /** 13 | * Creates a 2D array of null {@link Emoji}s, with the given dimensions. 14 | * 15 | * @param width The width of the array 16 | * @param height The height of the array 17 | * @return The 2D array 18 | */ 19 | public static Emoji[][] getNullGrid(int width, int height) { 20 | var rows = new Emoji[height][]; 21 | for (int i = 0; i < height; i++) rows[i] = new Emoji[width]; 22 | return rows; 23 | } 24 | 25 | /** 26 | * Creates a 2D array of the {@link Emoji} {@link StaticEmoji#DISCORD}, with the given dimensions. 27 | * 28 | * @param width The width of the array 29 | * @param height The height of the array 30 | * @return The 2D array 31 | */ 32 | public static Emoji[][] getEmptyGrid(int width, int height) { 33 | return getEmptyGrid(StaticEmoji.TRANSPARENT, width, height); 34 | } 35 | 36 | /** 37 | * Creates a 2D array of the given {@link Emoji} with the given dimensions. 38 | * 39 | * @param emoji The emoji to fill the grid with 40 | * @param width The width of the array 41 | * @param height The height of the array 42 | * @return The 2D array 43 | */ 44 | public static Emoji[][] getEmptyGrid(Emoji emoji, int width, int height) { 45 | if (emoji == null) return getNullGrid(width, height); 46 | var rows = new Emoji[height][]; 47 | for (int y = 0; y < height; y++) { 48 | rows[y] = new Emoji[width]; 49 | Arrays.fill(rows[y], emoji); 50 | } 51 | return rows; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/DefaultEmojiComponent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | 6 | import static com.uddernetworks.emojide.gui.components.ComponentUtils.getNullGrid; 7 | 8 | /** 9 | * A superclass for every component that can be rendered on the screen, i.e. containers, text, etc. 10 | */ 11 | public abstract class DefaultEmojiComponent implements EmojiComponent { 12 | protected final Displayer displayer; 13 | protected final EmojiManager emojiManager; 14 | protected final int width; 15 | protected final int height; 16 | private boolean clearCache; 17 | private EmojiComponent parent; 18 | private Emoji[][] lastRender; 19 | 20 | public DefaultEmojiComponent(Displayer displayer, int width, int height) { 21 | this.displayer = displayer; 22 | this.emojiManager = displayer.getEmojiManager(); 23 | this.width = width; 24 | this.height = height; 25 | } 26 | 27 | @Override 28 | public Emoji[][] getCachedRender() { 29 | if (!clearCache && this.lastRender != null) return this.lastRender; 30 | this.clearCache = false; 31 | return this.lastRender = render(getNullGrid(this.width, this.height)); 32 | } 33 | 34 | @Override 35 | public int getWidth() { 36 | return width; 37 | } 38 | 39 | @Override 40 | public int getHeight() { 41 | return height; 42 | } 43 | 44 | @Override 45 | public EmojiComponent getParent() { 46 | return parent; 47 | } 48 | 49 | @Override 50 | public void setParent(EmojiComponent parent) { 51 | this.parent = parent; 52 | } 53 | 54 | @Override 55 | public void clearCache() { 56 | this.clearCache = true; 57 | if (this.parent != null) this.parent.clearCache(); 58 | } 59 | 60 | @Override 61 | public void update() {} 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/DefaultPositionedComponent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | public class DefaultPositionedComponent implements PositionedComponent { 4 | 5 | private final EmojiComponent component; 6 | 7 | private int x; 8 | private int y; 9 | 10 | public DefaultPositionedComponent(EmojiComponent component, int x, int y) { 11 | this.component = component; 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | @Override 17 | public EmojiComponent getComponent() { 18 | return component; 19 | } 20 | 21 | @Override 22 | public void setPosition(int x, int y) { 23 | this.x = x; 24 | this.y = y; 25 | } 26 | 27 | @Override 28 | public int getX() { 29 | return x; 30 | } 31 | 32 | @Override 33 | public void setX(int x) { 34 | this.x = x; 35 | } 36 | 37 | @Override 38 | public int getY() { 39 | return y; 40 | } 41 | 42 | @Override 43 | public void setY(int y) { 44 | this.y = y; 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/Displayer.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.DefaultEmojiManager; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | import com.uddernetworks.emojide.main.EmojIDE; 6 | import net.dv8tion.jda.api.entities.TextChannel; 7 | 8 | public interface Displayer { 9 | /** 10 | * Sets the child being displayed by the {@link Displayer} without invoking {@link #update()}. 11 | * 12 | * @param component The child to render 13 | */ 14 | void setChild(EmojiComponent component); 15 | 16 | /** 17 | * Sets the child being displayed by the {@link Displayer} with invoking {@link #update()} if autoUpdate is true. 18 | * 19 | * @param component The child to render 20 | * @param autoUpdate If the {@link #update()} method should be invoked immediately after 21 | */ 22 | void setChild(EmojiComponent component, boolean autoUpdate); 23 | 24 | /** 25 | * Gets the child being displayed by the {@link Displayer}. 26 | * 27 | * @return The child being displayed 28 | */ 29 | EmojiComponent getChild(); 30 | 31 | /** 32 | * If the {@link Displayer} has started rendering items. 33 | * 34 | * @return If items have started to render 35 | */ 36 | boolean isDisplaying(); 37 | 38 | /** 39 | * Re-renders the changed lines by invoking {@link EmojiComponent#getCachedRender()} and either sending or editing 40 | * (only changed) lines rendered. 41 | */ 42 | void update(); 43 | 44 | /** 45 | * Removes all rendered messages. 46 | * 47 | * @param channel The {@link TextChannel} the messages are in 48 | */ 49 | void stop(TextChannel channel); 50 | 51 | /** 52 | * Gets the {@link EmojIDE}. 53 | * 54 | * @return The {@link EmojIDE} 55 | */ 56 | EmojIDE getEmojIDE(); 57 | 58 | /** 59 | * Gets the {@link DefaultEmojiManager} 60 | * 61 | * @return The {@link DefaultEmojiManager} 62 | */ 63 | EmojiManager getEmojiManager(); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/EmojiComponent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | 5 | /** 6 | * A component that is rendered by a {@link Displayer} upon invoking {@link #getCachedRender()}. 7 | */ 8 | public interface EmojiComponent { 9 | 10 | /** 11 | * Gets a 2D array of Emojis, [0].length being `height` and [][0].length being `width` set in the constructor. 12 | * 13 | * @param initial The initial Emoji grid to build on top of 14 | * @return The emojis to render 15 | */ 16 | Emoji[][] render(Emoji[][] initial); 17 | 18 | /** 19 | * Gets the rendered view of the component, or the most recent cached version if applicable. This is preferred 20 | * over {@link #render(Emoji[][])} due to caching. 21 | * 22 | * @return The emojis to render 23 | */ 24 | Emoji[][] getCachedRender(); 25 | 26 | /** 27 | * Gets the width of the component. 28 | * 29 | * @return The width of the component. 30 | */ 31 | int getWidth(); 32 | 33 | /** 34 | * Gets the height of the component. 35 | * 36 | * @return The height of the component. 37 | */ 38 | int getHeight(); 39 | 40 | /** 41 | * Gets the parent of the component. 42 | * 43 | * @return The parent component 44 | */ 45 | EmojiComponent getParent(); 46 | 47 | /** 48 | * Sets the parent of the component. 49 | * 50 | * @param parent The parent to set 51 | */ 52 | void setParent(EmojiComponent parent); 53 | 54 | /** 55 | * If the element should be updated during the next render. If the component has children that have cleared caches, 56 | * it's vital that all parents have cleared caches as well. 57 | */ 58 | void clearCache(); 59 | 60 | /** 61 | * Updates the current component if {@link #clearCache()} has been invoked. All children, children's children, 62 | * etc. will be updated forcibly. This could be expensive. 63 | */ 64 | void update(); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/EmojiContainer.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * A component that is rendered by a {@link Displayer} upon invoking {@link #getCachedRender()}. A container can also 7 | * render other children {@link EmojiComponent}s at arbitrary positions. 8 | */ 9 | public interface EmojiContainer extends EmojiComponent { 10 | 11 | /** 12 | * Adds a child {@link EmojiComponent} to render at the given coordinates. 13 | * 14 | * @param component The {@link EmojiComponent} to render 15 | * @param x The relative X coordinate to render at 16 | * @param y The relative Y coordinate to render at 17 | * @return The current {@link EmojiContainer} for builder-style invocations 18 | */ 19 | EmojiContainer addChild(EmojiComponent component, int x, int y); 20 | 21 | /** 22 | * Removes the given {@link EmojiComponent} as a child. 23 | * 24 | * @param component The {@link EmojiComponent} to remove 25 | */ 26 | void removeChild(EmojiComponent component); 27 | 28 | /** 29 | * Gets an immutable list of all {@link PositionedComponent} children. 30 | * 31 | * @return An immutable list of all children 32 | */ 33 | List getChildren(); 34 | 35 | /** 36 | * Positions an existing {@link EmojiComponent} to the given coordinates. 37 | * 38 | * @param component The {@link EmojiComponent} to mutate the location of 39 | * @param x The relative X coordinate to render at 40 | * @param y The relative Y coordinate to render at 41 | * @return The current {@link EmojiContainer} for builder-style invocations 42 | */ 43 | EmojiContainer positionChild(EmojiComponent component, int x, int y); 44 | 45 | /** 46 | * Sets the offset elements will be placed at internally. 47 | * 48 | * @param x The X offset 49 | * @param y The Y offset 50 | */ 51 | void setOffset(int x, int y); 52 | 53 | /** 54 | * Gets the X offset set by {@link #setOffset(int, int)}; 55 | * 56 | * @return The X offset 57 | */ 58 | int getXOffset(); 59 | 60 | /** 61 | * Gets the Y offset set by {@link #setOffset(int, int)}; 62 | * 63 | * @return The Y offset 64 | */ 65 | int getYOffset(); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/InvalidComponentException.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | /** 4 | * Called when a component has invalid dimensions or is in an otherwise un-renderable state. 5 | */ 6 | public class InvalidComponentException extends RuntimeException { 7 | public InvalidComponentException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/PositionedComponent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components; 2 | 3 | /** 4 | * An object to store an {@link EmojiComponent}'s coordinates among whatever container using it. 5 | */ 6 | public interface PositionedComponent { 7 | 8 | /** 9 | * Gets the {@link EmojiComponent} stored. 10 | * 11 | * @return The {@link EmojiComponent} stored 12 | */ 13 | EmojiComponent getComponent(); 14 | 15 | /** 16 | * Sets the position of the component. 17 | * 18 | * @param x The X position 19 | * @param y The Y position 20 | */ 21 | void setPosition(int x, int y); 22 | 23 | /** 24 | * Gets the X coordinate of the component. 25 | * 26 | * @return The X coordinate of the component 27 | */ 28 | int getX(); 29 | 30 | /** 31 | * Sets the X coordinate of the component. 32 | * 33 | * @param x The X coordinate to set 34 | */ 35 | void setX(int x); 36 | 37 | /** 38 | * Gets the Y coordinate of the component. 39 | * 40 | * @return The Y coordinate of the component 41 | */ 42 | int getY(); 43 | 44 | /** 45 | * Sets the Y coordinate of the component. 46 | * 47 | * @param y The Y coordinate to set 48 | */ 49 | void setY(int y); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/output/DefaultOutputFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.output; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 5 | import com.uddernetworks.emojide.gui.StaticTextFrame; 6 | 7 | import java.util.Arrays; 8 | 9 | public class DefaultOutputFrame implements OutputFrameTheme { 10 | 11 | private OutputFrame frame; 12 | 13 | public DefaultOutputFrame(OutputFrame frame) { 14 | this.frame = frame; 15 | } 16 | 17 | @Override 18 | public Emoji[][] render(Emoji[][] initial) { 19 | Arrays.fill(initial[0], StaticEmoji.CTABBED_FRAME); 20 | return initial; 21 | } 22 | 23 | @Override 24 | public void setOutput(StaticTextFrame output) { 25 | frame.addChild(output, 1, 1); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/output/IntelliJOutputFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.output; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.StaticTextFrame; 5 | 6 | public class IntelliJOutputFrame implements OutputFrameTheme { 7 | 8 | private OutputFrame frame; 9 | 10 | public IntelliJOutputFrame(OutputFrame frame) { 11 | this.frame = frame; 12 | } 13 | 14 | @Override 15 | public Emoji[][] render(Emoji[][] initial) { 16 | return initial; 17 | } 18 | 19 | @Override 20 | public void setOutput(StaticTextFrame output) { 21 | frame.addChild(output, 1, 0); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/output/OutputFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.output; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.StaticTextFrame; 5 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 6 | import com.uddernetworks.emojide.gui.components.Displayer; 7 | import com.uddernetworks.emojide.gui.components.theme.ThemeDependantRendering; 8 | 9 | public class OutputFrame extends DefaultEmojiContainer { 10 | 11 | private OutputFrameTheme theme; 12 | 13 | public OutputFrame(Displayer displayer, int width, int height) { 14 | super(displayer, width, height); 15 | theme = ThemeDependantRendering.getImplementation(this); 16 | } 17 | 18 | @Override 19 | public Emoji[][] render(Emoji[][] initial) { 20 | return super.render(theme.render(initial)); 21 | } 22 | 23 | public OutputFrame setOutput(StaticTextFrame output) { 24 | theme.setOutput(output); 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/output/OutputFrameTheme.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.output; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.StaticTextFrame; 5 | import com.uddernetworks.emojide.gui.components.theme.ThemeImplementor; 6 | 7 | public interface OutputFrameTheme extends ThemeImplementor { 8 | Emoji[][] render(Emoji[][] initial); 9 | void setOutput(StaticTextFrame output); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/styled/StyleUtils.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.styled; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 5 | 6 | import java.util.Arrays; 7 | 8 | import static com.uddernetworks.emojide.gui.components.ComponentUtils.getEmptyGrid; 9 | 10 | /** 11 | * Utilities used by {@link StyledEmojiComponent} instances. 12 | */ 13 | public class StyleUtils { 14 | /** 15 | * Gets the styled base by this class, to be added onto by other instances. 16 | * 17 | * @param background The {@link Emoji} background 18 | * @param border The single-width {@link Emoji} border 19 | * @param width The width 20 | * @param height The height 21 | * @return The initial Emoji grid 22 | */ 23 | public static Emoji[][] renderInitialStyle(Emoji background, Emoji border, int width, int height) { 24 | return drawBorder(getEmptyGrid(background, width, height), border); 25 | } 26 | 27 | /** 28 | * Draws a border of a given {@link Emoji} with a width of 1 in the given 2D array. 29 | * 30 | * @param rows The 2D array to (presumably) render 31 | * @param border The {@link Emoji} to set as the border 32 | * @return The mutated 2D array 33 | */ 34 | public static Emoji[][] drawBorder(Emoji[][] rows, Emoji border) { 35 | if (border == null) return rows; 36 | Arrays.fill(rows[0], border); 37 | Arrays.fill(rows[rows.length - 1], border); 38 | for (int y = 0; y < rows.length; y++) { 39 | rows[y][0] = border; 40 | rows[y][rows[0].length - 1] = border; 41 | } 42 | return rows; 43 | } 44 | 45 | /** 46 | * Creates a thin, light gray border with {@link StaticEmoji#LTABBED_FRAME}, {@link StaticEmoji#RTABBED_FRAME}, 47 | * {@link StaticEmoji#TTABBED_FRAME}, and {@link StaticEmoji#BTABBED_FRAME},. 48 | * 49 | * @param rows The 2D array to (presumably) render 50 | * @return The mutated 2D array 51 | */ 52 | public static Emoji[][] drawThinBorder(Emoji[][] rows) { 53 | for (int y = 1; y < rows.length - 1; y++) { 54 | rows[y][0] = StaticEmoji.LTABBED_FRAME; 55 | rows[y][rows[0].length - 1] = StaticEmoji.RTABBED_FRAME; 56 | } 57 | 58 | Arrays.fill(rows[0], 1, rows[0].length - 1, StaticEmoji.TTABBED_FRAME); 59 | Arrays.fill(rows[rows.length - 1], 1, rows[rows.length - 1].length - 1, StaticEmoji.BTABBED_FRAME); 60 | return rows; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/styled/StyledEmojiComponent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.styled; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.DefaultEmojiComponent; 5 | import com.uddernetworks.emojide.gui.components.Displayer; 6 | 7 | import static com.uddernetworks.emojide.gui.components.styled.StyleUtils.renderInitialStyle; 8 | 9 | /** 10 | * A {@link DefaultEmojiComponent} with the ability to have a border and background. 11 | */ 12 | public abstract class StyledEmojiComponent extends DefaultEmojiComponent { 13 | 14 | private Emoji border; 15 | private Emoji background; 16 | 17 | public StyledEmojiComponent(Displayer displayer, int width, int height) { 18 | super(displayer, width, height); 19 | } 20 | 21 | @Override 22 | public Emoji[][] render(Emoji[][] initial) { 23 | return renderInitialStyle(this.background, this.border, this.width, this.height); 24 | } 25 | 26 | public Emoji getBorder() { 27 | return border; 28 | } 29 | 30 | public StyledEmojiComponent setBorder(Emoji border) { 31 | this.border = border; 32 | return this; 33 | } 34 | 35 | public Emoji getBackground() { 36 | return background; 37 | } 38 | 39 | public StyledEmojiComponent setBackground(Emoji background) { 40 | this.background = background; 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/styled/StyledEmojiContainer.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.styled; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 5 | import com.uddernetworks.emojide.gui.components.Displayer; 6 | 7 | import static com.uddernetworks.emojide.gui.components.styled.StyleUtils.renderInitialStyle; 8 | 9 | /** 10 | * A {@link DefaultEmojiContainer} with the ability to have a border and background. 11 | */ 12 | public abstract class StyledEmojiContainer extends DefaultEmojiContainer { 13 | 14 | private Emoji border; 15 | private Emoji background; 16 | 17 | public StyledEmojiContainer(Displayer displayer, int width, int height) { 18 | super(displayer, width, height); 19 | } 20 | 21 | @Override 22 | public Emoji[][] render(Emoji[][] initial) { 23 | return super.render(renderInitialStyle(this.background, this.border, this.width, this.height)); 24 | } 25 | 26 | public Emoji getBorder() { 27 | return border; 28 | } 29 | 30 | public StyledEmojiContainer setBorder(Emoji border) { 31 | this.border = border; 32 | return this; 33 | } 34 | 35 | public Emoji getBackground() { 36 | return background; 37 | } 38 | 39 | public StyledEmojiContainer setBackground(Emoji background) { 40 | this.background = background; 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/theme/ThemeDependantRendering.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.theme; 2 | 3 | import com.uddernetworks.emojide.gui.theme.Theme; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.function.Function; 10 | 11 | public class ThemeDependantRendering { 12 | 13 | private static EmojIDE emojIDE; 14 | private static Map, Map> themeImpls = new HashMap<>(); 15 | // Oh jesus christ what the fuck is this 16 | private static Map, Map>> themeConstants = new HashMap<>(); 17 | 18 | public ThemeDependantRendering(EmojIDE emojIDE) { 19 | ThemeDependantRendering.emojIDE = emojIDE; 20 | } 21 | 22 | public static void registerImplementation(Class baseClass, Theme theme, Function implementation) { 23 | themeImpls.computeIfAbsent(baseClass, i -> new HashMap<>()).put(theme, implementation); 24 | } 25 | 26 | public static T getImplementation(Object base) { 27 | var theme = emojIDE.getThemeManager().getActive(); 28 | var impl = themeImpls.getOrDefault(base.getClass(), Collections.emptyMap()).get(theme); 29 | if (impl == null) { 30 | throw new RuntimeException("A fatal exception has occurred! The class " + base.getClass().getCanonicalName() + " does not hold an implementation for the theme " + theme.getName()); 31 | } 32 | return (T) impl.apply(base); 33 | } 34 | 35 | public static void setThemeConstant(Class implementorClass, Theme theme, Object key, Object value) { 36 | themeConstants.computeIfAbsent(implementorClass, i -> new HashMap<>()).computeIfAbsent(key, i -> new HashMap<>()).put(theme, value); 37 | } 38 | 39 | public static V getThemeConstant(Class implementorClass, Object key) { 40 | var theme = emojIDE.getThemeManager().getActive(); 41 | var themeConstants = ThemeDependantRendering.themeConstants.getOrDefault(implementorClass, Collections.emptyMap()).get(key); 42 | if (themeConstants != null) { 43 | var themeValue = themeConstants.get(theme); 44 | if (themeValue != null) return (V) themeValue; 45 | } 46 | throw new RuntimeException("A fatal exception has occurred! The class " + implementorClass.getCanonicalName() + " does not hold a constant for the key " + key + " on theme " + theme.getName()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/components/theme/ThemeImplementor.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.components.theme; 2 | 3 | public interface ThemeImplementor { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/render/RenderAction.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.render; 2 | 3 | import net.dv8tion.jda.api.entities.Message; 4 | import net.dv8tion.jda.api.entities.MessageEmbed; 5 | import net.dv8tion.jda.api.entities.TextChannel; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * An action for the {@link RenderEngine} to do, either editing or creating a message with given contents. 11 | */ 12 | public class RenderAction implements RenderEntry { 13 | 14 | private String content; 15 | private MessageEmbed embedContent; 16 | 17 | private Message editing; 18 | private TextChannel channel; 19 | 20 | private Consumer onComplete; 21 | 22 | public RenderAction(Message editing, String content, Consumer onComplete) { 23 | this(editing, content, null, onComplete); 24 | } 25 | 26 | public RenderAction(TextChannel channel, String content, Consumer onComplete) { 27 | this(null, content, channel, onComplete); 28 | } 29 | 30 | public RenderAction(Message editing, MessageEmbed embedContent, Consumer onComplete) { 31 | this(editing, embedContent, null, onComplete); 32 | } 33 | 34 | public RenderAction(TextChannel channel, MessageEmbed embedContent, Consumer onComplete) { 35 | this(null, embedContent, channel, onComplete); 36 | } 37 | 38 | public RenderAction(Message editing, MessageEmbed embedContent, TextChannel channel, Consumer onComplete) { 39 | this.editing = editing; 40 | this.embedContent = embedContent; 41 | this.channel = channel; 42 | this.onComplete = onComplete; 43 | } 44 | 45 | public RenderAction(Message editing, String content, TextChannel channel, Consumer onComplete) { 46 | this.editing = editing; 47 | this.content = content; 48 | this.channel = channel; 49 | this.onComplete = onComplete; 50 | } 51 | 52 | /** 53 | * Gets if the action is editing a message. 54 | * 55 | * @return If the action is editing a message 56 | */ 57 | public boolean isEditing() { 58 | return this.editing != null; 59 | } 60 | 61 | /** 62 | * Gets the new content of the message. 63 | * 64 | * @return The new content of the message 65 | */ 66 | public String getContent() { 67 | return content; 68 | } 69 | 70 | /** 71 | * Gets the new embedded content of the message. 72 | * 73 | * @return The new embedded content of the message 74 | */ 75 | public MessageEmbed getEmbedContent() { 76 | return embedContent; 77 | } 78 | 79 | /** 80 | * Gets the message to be edited, if editing. 81 | * 82 | * @return The message being edited 83 | */ 84 | public Message getEditing() { 85 | return editing; 86 | } 87 | 88 | /** 89 | * Gets the {@link TextChannel} to send the message, if sending. 90 | * 91 | * @return The {@link TextChannel} to send the message 92 | */ 93 | public TextChannel getChannel() { 94 | return channel; 95 | } 96 | 97 | /** 98 | * Invokes the (If existent) given completed consumer, giving the edited or created {@link Message}. 99 | * 100 | * @param message The edited or created {@link Message} 101 | */ 102 | public void complete(Message message) { 103 | onComplete.accept(message); 104 | } 105 | 106 | /** 107 | * Gets the message ID of the message being edited, or -1 if the action is not editing. 108 | * 109 | * @return The message ID 110 | */ 111 | public long getEditingId() { 112 | return editing == null ? -1 : editing.getIdLong(); 113 | } 114 | 115 | @Override 116 | public boolean isAction() { 117 | return true; 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return "Action[" + content + "]"; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/render/RenderBreakpoint.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.render; 2 | 3 | /** 4 | * Stores a {@link Runnable} to run when the queue reaches it. 5 | */ 6 | public class RenderBreakpoint implements RenderEntry { 7 | 8 | private Runnable runnable; 9 | 10 | public RenderBreakpoint(Runnable runnable) { 11 | this.runnable = runnable; 12 | } 13 | 14 | /** 15 | * Runs the runnable 16 | */ 17 | public void run() { 18 | runnable.run(); 19 | } 20 | 21 | @Override 22 | public boolean isAction() { 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/render/RenderEntry.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.render; 2 | 3 | /** 4 | * An entry for something to be processed in the {@link RenderEngine}. 5 | */ 6 | public interface RenderEntry { 7 | 8 | /** 9 | * If the entry is an instance of {@link RenderAction}. 10 | * 11 | * @return If the entry is an action 12 | */ 13 | boolean isAction(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabbed/DefaultTabbedFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabbed; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 5 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 6 | import com.uddernetworks.emojide.gui.components.theme.ThemeDependantRendering; 7 | import com.uddernetworks.emojide.gui.tabs.Tab; 8 | import com.uddernetworks.emojide.gui.theme.Theme; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.Stream; 14 | 15 | import static com.uddernetworks.emojide.gui.tabbed.TabbedFrameConstants.AVAILABLE_TEXT_HEIGHT; 16 | import static com.uddernetworks.emojide.gui.tabbed.TabbedFrameConstants.CONSOLE_OFFSET; 17 | 18 | public class DefaultTabbedFrame implements TabbedFrameTheme { 19 | 20 | static { 21 | ThemeDependantRendering.setThemeConstant(TabbedFrame.class, Theme.DEFAULT, AVAILABLE_TEXT_HEIGHT, 16); 22 | ThemeDependantRendering.setThemeConstant(TabbedFrame.class, Theme.DEFAULT, CONSOLE_OFFSET, 16); 23 | } 24 | 25 | private TabbedFrame frame; 26 | 27 | public DefaultTabbedFrame(TabbedFrame frame) { 28 | this.frame = frame; 29 | } 30 | 31 | @Override 32 | public void settingOffset() { 33 | frame.setOffset(1, 2); 34 | } 35 | 36 | @Override 37 | public void addingChildTab(EmojiComponent component) { 38 | frame.addChild(component, 0, 0); 39 | } 40 | 41 | @Override 42 | public Emoji[][] drawBorder(Emoji[][] rows) { 43 | for (int y = 2; y < rows.length - 1; y++) { 44 | rows[y][0] = StaticEmoji.LTABBED_FRAME; 45 | rows[y][rows[0].length - 1] = StaticEmoji.RTABBED_FRAME; 46 | } 47 | 48 | Arrays.fill(rows[0], 1, rows[0].length - 1, StaticEmoji.TRANSPARENT); 49 | Arrays.fill(rows[1], 1, rows[1].length - 1, StaticEmoji.TTABBED_FRAME); 50 | 51 | var headers = drawHeaders(); 52 | for (int i = 0; i < headers.length; i++) { 53 | System.arraycopy(headers[i], 0, rows[i], 2, headers[i].length); 54 | } 55 | 56 | Arrays.fill(rows[rows.length - 1], 1, rows[rows.length - 1].length - 1, StaticEmoji.BTABBED_FRAME); 57 | 58 | // Corners 59 | rows[1][0] = StaticEmoji.BR_FRAME; 60 | rows[1][rows[0].length - 1] = StaticEmoji.BL_FRAME; 61 | rows[rows.length - 1][0] = StaticEmoji.TR_FRAME; 62 | rows[rows.length - 1][rows[0].length - 1] = StaticEmoji.TL_FRAME; 63 | return rows; 64 | } 65 | 66 | private Emoji[][] drawHeaders() { 67 | var thisWidth = frame.getWidth() - 4; 68 | var upper = new ArrayList(); 69 | var row = new ArrayList(); 70 | 71 | var activeTab = frame.getActive(); 72 | for (Tab tab : frame.tabs) { 73 | var active = tab.equals(activeTab); 74 | if (row.size() + 4 + tab.getName().length() > thisWidth) break; 75 | 76 | row.add(active ? StaticEmoji.LTAB_SEPARATOR_SELECTED : StaticEmoji.LTAB_SEPARATOR); 77 | upper.add(active ? StaticEmoji.LTAB_CORNER_SELECTED : StaticEmoji.BR_FRAME); 78 | 79 | for (char cha : tab.getName().toCharArray()) { 80 | row.add(frame.getEmojiManager().getTextEmoji(cha)); 81 | upper.add(active ? StaticEmoji.TTABBED_FRAME_SELECTED : StaticEmoji.TTABBED_FRAME); 82 | } 83 | 84 | row.add(active ? StaticEmoji.RTAB_SEPARATOR_SELECTED : StaticEmoji.RTAB_SEPARATOR); 85 | upper.add(active ? StaticEmoji.RTAB_CORNER_SELECTED : StaticEmoji.BL_FRAME); 86 | } 87 | 88 | for (int i = 0; i < thisWidth - row.size(); i++) { 89 | row.add(StaticEmoji.TTABBED_FRAME); 90 | upper.add(StaticEmoji.TRANSPARENT); 91 | } 92 | 93 | return Stream.of(upper, row).map(list -> list.toArray(Emoji[]::new)).collect(Collectors.toList()).toArray(Emoji[][]::new); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabbed/IntelliJTabbedFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabbed; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.Group; 5 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 6 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 7 | import com.uddernetworks.emojide.gui.components.theme.ThemeDependantRendering; 8 | import com.uddernetworks.emojide.gui.tabs.Tab; 9 | import com.uddernetworks.emojide.gui.theme.Theme; 10 | 11 | import static com.uddernetworks.emojide.gui.tabbed.TabbedFrameConstants.AVAILABLE_TEXT_HEIGHT; 12 | 13 | public class IntelliJTabbedFrame implements TabbedFrameTheme { 14 | 15 | static { 16 | ThemeDependantRendering.setThemeConstant(TabbedFrame.class, Theme.INTELLIJ, AVAILABLE_TEXT_HEIGHT, 11); 17 | } 18 | 19 | private TabbedFrame frame; 20 | 21 | public IntelliJTabbedFrame(TabbedFrame frame) { 22 | this.frame = frame; 23 | } 24 | 25 | @Override 26 | public void settingOffset() { 27 | frame.setOffset(1, 4); 28 | } 29 | 30 | @Override 31 | public void addingChildTab(EmojiComponent component) { 32 | frame.addChild(component, 0, 0); 33 | } 34 | 35 | @Override 36 | public Emoji[][] drawBorder(Emoji[][] rows) { 37 | Group.INTELLIJ_BASE.getEmojis().forEach(emoji -> { 38 | var split = emoji.getName().split("j"); 39 | rows[Integer.parseInt(split[1])][Integer.parseInt(split[0])] = emoji; 40 | }); 41 | 42 | var headers = drawHeaders(); 43 | for (int y = 0; y < headers.length; y++) { 44 | 45 | var thisRow = headers[y]; 46 | var copyingTo = rows[y + 1]; 47 | for (int x = 0; x < thisRow.length; x++) { 48 | var thisEmoji = thisRow[x]; 49 | if (thisEmoji != null && thisEmoji != StaticEmoji.TRANSPARENT) copyingTo[x] = thisEmoji; 50 | } 51 | 52 | rows[y + 1] = copyingTo; 53 | } 54 | return rows; 55 | } 56 | 57 | private Emoji[][] drawHeaders() { 58 | var top = new Emoji[frame.getWidth()]; 59 | var row = new Emoji[frame.getWidth()]; 60 | var bottom = new Emoji[frame.getWidth()]; 61 | 62 | var activeFont = frame.getEmojIDE().getFontManager().getActive().ordinal(); 63 | var unselectedFontUsing = new String[]{"t", "ft"}[activeFont]; 64 | var selectedFontUsing = new String[]{"e", "fe"}[activeFont]; 65 | 66 | int emojiIndex = 1; 67 | var activeTab = frame.getActive(); 68 | for (Tab tab : frame.tabs) { 69 | var active = tab.equals(activeTab); 70 | 71 | if (active) { 72 | top[emojiIndex] = StaticEmoji.IJ_SELECTED_TOP_LEFT; 73 | row[emojiIndex] = StaticEmoji.IJ_SELECTED_MIDDLE_LEFT; 74 | bottom[emojiIndex] = StaticEmoji.IJ_SELECTED_BOTTOM_LEFT; 75 | } 76 | emojiIndex++; 77 | 78 | for (char cha : tab.getName().toCharArray()) { 79 | var prefix = active ? selectedFontUsing : unselectedFontUsing; 80 | row[emojiIndex] = frame.getEmojiManager().getEmoji(prefix + (int) cha); 81 | 82 | if (active) { 83 | top[emojiIndex] = StaticEmoji.IJ_SELECTED_TOP; 84 | bottom[emojiIndex] = StaticEmoji.IJ_SELECTED_BOTTOM; 85 | } 86 | emojiIndex++; 87 | } 88 | 89 | if (active) { 90 | top[emojiIndex] = StaticEmoji.IJ_SELECTED_TOP_RIGHT; 91 | row[emojiIndex] = StaticEmoji.IJ_SELECTED_MIDDLE_RIGHT; 92 | bottom[emojiIndex] = StaticEmoji.IJ_SELECTED_BOTTOM_RIGHT; 93 | } 94 | emojiIndex++; 95 | } 96 | 97 | return new Emoji[][] {top, row, bottom}; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabbed/TabbedFrame.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabbed; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 6 | import com.uddernetworks.emojide.event.Handler; 7 | import com.uddernetworks.emojide.gui.components.DefaultEmojiContainer; 8 | import com.uddernetworks.emojide.gui.components.Displayer; 9 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 10 | import com.uddernetworks.emojide.gui.components.theme.ThemeDependantRendering; 11 | import com.uddernetworks.emojide.gui.tabs.Tab; 12 | import com.uddernetworks.emojide.keyboard.KeyPressEvent; 13 | import com.uddernetworks.emojide.keyboard.KeyboardInputManager; 14 | import com.uddernetworks.emojide.keyboard.KeyboardRaisable; 15 | import com.uddernetworks.emojide.main.EmojIDE; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class TabbedFrame extends DefaultEmojiContainer { 23 | 24 | private static Logger LOGGER = LoggerFactory.getLogger(TabbedFrame.class); 25 | 26 | private TabbedFrameTheme theme; 27 | private KeyboardInputManager keyboardInputManager; 28 | int activeTab = 0; 29 | List tabs = new ArrayList<>(); 30 | 31 | public TabbedFrame(Displayer displayer, int width, int height) { 32 | super(displayer, width, height); // Adds 2 due to header 33 | theme = ThemeDependantRendering.getImplementation(this); 34 | this.keyboardInputManager = displayer.getEmojIDE().getKeyboardInputManager(); 35 | KeyboardRaisable.get().addListener(this); 36 | theme.settingOffset(); 37 | } 38 | 39 | @Override 40 | public Emoji[][] render(Emoji[][] initial) { 41 | // Usually it would be theme.drawBorder(super.render(...)) but in this case we need the tabbed frame to be on top, 42 | // which should be the case for all non-welcome tabs 43 | return getActive().getName().equals("Welcome") ? super.render(theme.drawBorder(initial)) : theme.drawBorder(super.render(initial)); 44 | } 45 | 46 | @Handler(event = "keyboard") 47 | private void onKeyPress(KeyPressEvent event) { 48 | if (this.keyboardInputManager.getState() == KeyboardInputManager.ActiveState.ALT) { 49 | if (event.isAlphanumeric()) return; 50 | if (event.getStaticEmoji() == StaticEmoji.RIGHT) { 51 | var from = getActive(); 52 | if (++activeTab >= tabs.size()) activeTab = 0; 53 | switchToTab(from, getActive()); 54 | } else if (event.getStaticEmoji() == StaticEmoji.LEFT) { 55 | var from = getActive(); 56 | if (--activeTab < 0) activeTab = tabs.size() - 1; 57 | switchToTab(from, getActive()); 58 | } 59 | } 60 | } 61 | 62 | public void selectTab(EmojiComponent component) { 63 | if (getActive().getComponent().equals(component)) return; 64 | tabs.stream().filter(tab -> tab.getComponent().equals(component)).findFirst().ifPresent(to -> switchToTab(getActive(), to)); 65 | } 66 | 67 | public Tab getFirst() { 68 | return tabs.get(0); 69 | } 70 | 71 | public void removeTab(Tab tab) { 72 | var active = getActive(); 73 | tabs.remove(activeTab); 74 | if (active.equals(tab)) switchToTab(active, getFirst()); 75 | refresh(); 76 | } 77 | 78 | private void switchToTab(Tab from, Tab to) { 79 | for (int i = 0; i < tabs.size(); i++) if (tabs.get(i).equals(to)) activeTab = i; 80 | from.deactivate(); 81 | removeChild(from.getComponent()); 82 | from.getComponent().clearCache(); 83 | to.activate(); 84 | theme.addingChildTab(to.getComponent()); 85 | to.getComponent().clearCache(); 86 | refresh(); 87 | } 88 | 89 | public Tab getActive() { 90 | return this.tabs.get(this.activeTab); 91 | } 92 | 93 | public TabbedFrame addTab(String name, EmojiComponent component) { 94 | addTab(name, component, false); 95 | return this; 96 | } 97 | 98 | public TabbedFrame addTab(String name, EmojiComponent component, boolean active) { 99 | if (this.tabs.isEmpty()) theme.addingChildTab(component); 100 | this.tabs.add(new Tab(name, component)); 101 | if (active) { 102 | LOGGER.info("{} is active, tabs size: {} name of active: {}", name, tabs.size(), getActive().getName()); 103 | selectTab(component); 104 | } 105 | return this; 106 | } 107 | 108 | public void refresh() { 109 | if (displayer.isDisplaying()) { 110 | clearCache(); 111 | update(); 112 | this.displayer.update(); 113 | } 114 | } 115 | 116 | public EmojiManager getEmojiManager() { 117 | return emojiManager; 118 | } 119 | 120 | public EmojIDE getEmojIDE() { 121 | return displayer.getEmojIDE(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabbed/TabbedFrameConstants.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabbed; 2 | 3 | public enum TabbedFrameConstants { 4 | AVAILABLE_TEXT_HEIGHT, 5 | CONSOLE_OFFSET, 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabbed/TabbedFrameTheme.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabbed; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 5 | import com.uddernetworks.emojide.gui.components.theme.ThemeImplementor; 6 | 7 | public interface TabbedFrameTheme extends ThemeImplementor { 8 | void settingOffset(); 9 | void addingChildTab(EmojiComponent component); 10 | Emoji[][] drawBorder(Emoji[][] initial); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/tabs/Tab.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.tabs; 2 | 3 | import com.uddernetworks.emojide.gui.components.EmojiComponent; 4 | import com.uddernetworks.emojide.gui.components.EmojiContainer; 5 | import com.uddernetworks.emojide.keyboard.KeyboardRaisable; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class Tab { 10 | 11 | private static Logger LOGGER = LoggerFactory.getLogger(Tab.class); 12 | 13 | private String name; 14 | private EmojiComponent component; 15 | 16 | public Tab(String name, EmojiComponent component) { 17 | this.name = name; 18 | this.component = component; 19 | } 20 | 21 | public void activate() { 22 | try { 23 | LOGGER.info("The tab {} is now active!", name); 24 | if (component instanceof EmojiContainer) { 25 | var children = ((EmojiContainer) component).getChildren(); 26 | if (children.isEmpty()) { 27 | KeyboardRaisable.get().addListener(component); 28 | return; 29 | } 30 | 31 | KeyboardRaisable.get().addListener(children.get(0).getComponent()); 32 | } else { 33 | KeyboardRaisable.get().addListener(component); 34 | } 35 | } catch (Exception e) { 36 | LOGGER.error("fuck", e); 37 | } 38 | } 39 | 40 | public void deactivate() { 41 | LOGGER.info("The tab {} is no longer active!", name); 42 | if (component instanceof EmojiContainer) { 43 | var children = ((EmojiContainer) component).getChildren(); 44 | if (children.isEmpty()) { 45 | KeyboardRaisable.get().removeListener(component); 46 | return; 47 | } 48 | 49 | KeyboardRaisable.get().removeListener(children.get(0).getComponent()); 50 | } else { 51 | KeyboardRaisable.get().removeListener(component); 52 | } 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public EmojiComponent getComponent() { 60 | return component; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/text/AutoGrowArrayList.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.text; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.function.Supplier; 8 | import java.util.stream.IntStream; 9 | 10 | public class AutoGrowArrayList extends ArrayList { 11 | private Supplier addingToGrow; 12 | 13 | public AutoGrowArrayList() { 14 | super(); 15 | } 16 | 17 | public AutoGrowArrayList(Supplier addingToGrow) { 18 | super(); 19 | this.addingToGrow = addingToGrow; 20 | } 21 | 22 | public AutoGrowArrayList(@NotNull Collection collection) { 23 | super(collection); 24 | } 25 | 26 | /** 27 | * The element to add when the list must grow. 28 | * 29 | * @param addingToGrow The element 30 | * @return The current {@link AutoGrowArrayList} object for chain invocation 31 | */ 32 | public AutoGrowArrayList setAddingToGrow(Supplier addingToGrow) { 33 | this.addingToGrow = addingToGrow; 34 | return this; 35 | } 36 | 37 | /** 38 | * Gets the element that will be added when the list must grow, this is by default `null`. 39 | * 40 | * @return The element 41 | */ 42 | public Supplier getAddingToGrow() { 43 | return addingToGrow; 44 | } 45 | 46 | private void growToSafe(int index) { 47 | IntStream.range(0, Math.max(index - size() + 1, 0)).forEach(i -> add(this.addingToGrow == null ? null : this.addingToGrow.get())); 48 | } 49 | 50 | @Override 51 | public E get(int index) { 52 | growToSafe(index); 53 | return super.get(index); 54 | } 55 | 56 | @Override 57 | public E set(int index, E element) { 58 | growToSafe(index); 59 | return super.set(index, element); 60 | } 61 | 62 | @Override 63 | public void add(int index, E element) { 64 | growToSafe(index); 65 | super.add(index, element); 66 | } 67 | 68 | @Override 69 | public boolean addAll(int index, Collection c) { 70 | growToSafe(index); 71 | return super.addAll(index, c); 72 | } 73 | 74 | @NotNull 75 | @Override 76 | public AutoGrowArrayList subList(int fromIndex, int toIndex) { 77 | return new AutoGrowArrayList<>(super.subList(fromIndex, toIndex)).setAddingToGrow(this.addingToGrow); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/theme/DefaultThemeManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.theme; 2 | 3 | import com.electronwill.nightconfig.core.file.FileConfig; 4 | import com.uddernetworks.emojide.main.EmojIDE; 5 | 6 | import java.util.function.Supplier; 7 | 8 | public class DefaultThemeManager implements ThemeManager { 9 | 10 | private EmojIDE emojIDE; 11 | private FileConfig config; 12 | private Theme active; 13 | 14 | public DefaultThemeManager(EmojIDE emojIDE) { 15 | this.emojIDE = emojIDE; 16 | config = EmojIDE.getConfigManager().getConfig(); 17 | 18 | int themeOrdinal = config.getOrElse("ide.theme", -1); 19 | if (themeOrdinal == -1) config.set("ide.theme", themeOrdinal = 0); 20 | active = Theme.values()[themeOrdinal]; 21 | } 22 | 23 | @Override 24 | public Theme getActive() { 25 | return active; 26 | } 27 | 28 | @Override 29 | public void setActive(Theme active) { 30 | config.set("ide.theme", active.ordinal()); 31 | this.active = active; 32 | } 33 | 34 | @Override 35 | public void forTheme(Runnable def, Runnable intellij) { 36 | new Runnable[] {def, intellij}[active.ordinal()].run(); 37 | } 38 | 39 | @Override 40 | public T getForTheme(Supplier def, Supplier intellij) { 41 | return (T) new Supplier[] {def, intellij}[active.ordinal()].get(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/theme/Theme.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.theme; 2 | 3 | import com.uddernetworks.emojide.main.ChoosableEnum; 4 | 5 | public enum Theme implements ChoosableEnum { 6 | DEFAULT("Default"), 7 | INTELLIJ("IntelliJ"); 8 | 9 | private String name; 10 | 11 | Theme(String name) { 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/gui/theme/ThemeManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.theme; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public interface ThemeManager { 6 | 7 | /** 8 | * Gets the active {@link Theme} for the IDE. 9 | * 10 | * @return The active {@link Theme} 11 | */ 12 | Theme getActive(); 13 | 14 | /** 15 | * Sets the given {@link Theme} to active. 16 | * 17 | * @param active The {@link Theme} to activate 18 | */ 19 | void setActive(Theme active); 20 | 21 | /** 22 | * Executes code depending on what the active theme is; only one of the runnables will be invoked. 23 | * 24 | * @param def The runnable to invoke if {@link Theme#DEFAULT} is active 25 | * @param intellij The runnable to invoke if {@link Theme#INTELLIJ} is active 26 | */ 27 | void forTheme(Runnable def, Runnable intellij); 28 | 29 | /** 30 | * Executes a supplier to get avalid depending on what the active theme is; only one of the suppliers will be 31 | * invoked. 32 | * 33 | * @param def The supplier to invoke if {@link Theme#DEFAULT} is active 34 | * @param intellij The supplier to invoke if {@link Theme#INTELLIJ} is active 35 | * @return The value from one of the suppliers 36 | * @param The type 37 | */ 38 | T getForTheme(Supplier def, Supplier intellij); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/ide/ConsolePiper.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.ide; 2 | 3 | import com.uddernetworks.emojide.gui.StaticTextFrame; 4 | import com.uddernetworks.emojide.utils.Commandline; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.File; 9 | import java.io.InputStream; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Scanner; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.function.Consumer; 15 | 16 | public class ConsolePiper { 17 | 18 | private static Logger LOGGER = LoggerFactory.getLogger(ConsolePiper.class); 19 | 20 | private final StaticTextFrame textFrame; 21 | private List running = new ArrayList<>(); 22 | 23 | public ConsolePiper(StaticTextFrame textFrame) { 24 | this.textFrame = textFrame; 25 | } 26 | 27 | public void pipeCommand(List command, File directory) { 28 | pipeCommand(command, directory, "Piped"); 29 | } 30 | 31 | public void pipeCommand(List command, File directory, String threadName) { 32 | Commandline.runInheritedCommand(command, directory, true, pipeCommand(threadName)); 33 | } 34 | 35 | public Consumer pipeCommand(Consumer processCreate) { 36 | return pipeCommand("Piped"); 37 | } 38 | 39 | public Consumer pipeCommand(String threadName) { 40 | if (!running.isEmpty()) { 41 | running.forEach(future -> future.cancel(true)); 42 | running.clear(); 43 | } 44 | textFrame.setText(""); 45 | textFrame.refresh(); 46 | 47 | return process -> { 48 | inheritIO(process.getInputStream(), threadName); 49 | inheritIO(process.getErrorStream(), threadName); 50 | }; 51 | } 52 | 53 | private void inheritIO(InputStream inputStream, String threadName) { 54 | running.add(CompletableFuture.runAsync(() -> { 55 | Scanner sc = new Scanner(inputStream); 56 | while (sc.hasNextLine()) { 57 | var line = sc.nextLine(); 58 | synchronized (textFrame) { 59 | LOGGER.info("[{}] {}", threadName, line); 60 | textFrame.setText(textFrame.getText() + line); 61 | textFrame.refresh(); 62 | } 63 | } 64 | })); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/ide/FunctionController.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.ide; 2 | 3 | import com.uddernetworks.emojide.event.Handler; 4 | import com.uddernetworks.emojide.gui.EmptyContainerFrame; 5 | import com.uddernetworks.emojide.gui.HighlightedTextFrame; 6 | import com.uddernetworks.emojide.gui.TextPromptFrame; 7 | import com.uddernetworks.emojide.gui.components.Displayer; 8 | import com.uddernetworks.emojide.gui.tabbed.TabbedFrame; 9 | import com.uddernetworks.emojide.keyboard.KeyPressEvent; 10 | import com.uddernetworks.emojide.keyboard.KeyboardInputManager; 11 | import com.uddernetworks.emojide.keyboard.KeyboardRaisable; 12 | import com.uddernetworks.emojide.main.EmojIDE; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.file.Files; 19 | import java.nio.file.StandardOpenOption; 20 | import java.util.Arrays; 21 | 22 | public class FunctionController { 23 | 24 | private static Logger LOGGER = LoggerFactory.getLogger(FunctionController.class); 25 | 26 | private EmojIDE emojIDE; 27 | private KeyboardInputManager keyboardInputManager; 28 | private ConsolePiper piper; 29 | private Displayer displayer; 30 | private TabbedFrame tabbedFrame; 31 | 32 | public FunctionController(EmojIDE emojIDE, Displayer displayer, TabbedFrame tabbedFrame, ConsolePiper piper) { 33 | this.emojIDE = emojIDE; 34 | this.displayer = displayer; 35 | this.tabbedFrame = tabbedFrame; 36 | 37 | this.keyboardInputManager = emojIDE.getKeyboardInputManager(); 38 | this.piper = piper; 39 | KeyboardRaisable.get().addListener(this); 40 | } 41 | 42 | @Handler(event = "keyboard") 43 | private void onKeyPress(KeyPressEvent event) { 44 | if (event.isAlphanumeric()) { 45 | LOGGER.info("FunctionController state: {}", keyboardInputManager.getState().name()); 46 | switch (keyboardInputManager.getState()) { 47 | case CTRL: 48 | switch (Character.toLowerCase(event.getCharacter())) { 49 | case 'r': 50 | event.setCancelled(true); 51 | var tab = tabbedFrame.getActive(); 52 | if (tab.getName().equals("Welcome")) return; 53 | LOGGER.info("Running {}", tab.getName()); 54 | 55 | var container = (EmptyContainerFrame) tab.getComponent(); 56 | var editorComponent = (HighlightedTextFrame) container.getChildren().get(0).getComponent(); 57 | var code = editorComponent.getTextBlock().getText(); 58 | LOGGER.info("Code = \n{}", code); 59 | 60 | try { 61 | var file = new File("executing/" + tab.getName()); 62 | file.getParentFile().mkdirs(); 63 | file.createNewFile(); 64 | Files.write(file.toPath(), code.getBytes(), StandardOpenOption.TRUNCATE_EXISTING); 65 | piper.pipeCommand(Arrays.asList("node", file.getAbsolutePath()), new File("/"), "Node"); 66 | } catch (IOException e) { 67 | LOGGER.error("An error occurred while executing the code for tab " + tab.getName(), e); 68 | } 69 | break; 70 | case 'n': 71 | event.setCancelled(true); 72 | KeyboardRaisable.get().suspendListeners(); 73 | 74 | var innerWidth = tabbedFrame.getWidth() - tabbedFrame.getXOffset(); 75 | var innerHeight = tabbedFrame.getHeight() - tabbedFrame.getYOffset(); 76 | 77 | var prompt = new TextPromptFrame(displayer, "Name:"); 78 | tabbedFrame.addChild(prompt, (innerWidth / 2) - (prompt.getWidth() / 2), (innerHeight / 2) - (prompt.getHeight() / 2)); 79 | prompt.onEnter(name -> { 80 | LOGGER.info("Name of file: {}", name); 81 | prompt.deactivate(); 82 | tabbedFrame.removeChild(prompt); 83 | emojIDE.getDocumentManager().createDocument(name, 0).thenAccept(emojIDE.getDocumentTabController()::addTab); 84 | }); 85 | 86 | tabbedFrame.clearCache(); 87 | displayer.update(); 88 | break; 89 | case 'x': 90 | event.setCancelled(true); 91 | var active = tabbedFrame.getActive(); 92 | LOGGER.info("Active: {}", active.getName()); 93 | if (active.getName().equals("Welcome")) break; 94 | LOGGER.info("Deleting tab {}", active.getName()); 95 | emojIDE.getDocumentTabController().removeTab(active); 96 | break; 97 | } 98 | break; 99 | } 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/ide/LanguageHighlighter.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.ide; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.discord.emoji.EmojiManager; 5 | import com.uddernetworks.emojide.gui.text.TextBlock; 6 | import com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer; 7 | import com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser; 8 | import org.antlr.v4.runtime.*; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.awt.*; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | 18 | public class LanguageHighlighter { 19 | 20 | private static Logger LOGGER = LoggerFactory.getLogger(LanguageHighlighter.class); 21 | 22 | public static final Map COLORS = Map.of( 23 | 0x000000, "", // White 24 | 0xCC7832, "o", // Orange 25 | 0x6A8759, "g", // Green 26 | 0x6897BB, "b", // Blue 27 | 0x808080, "a", // Gray 28 | 0x666666, "l" // Light Gray 29 | ); 30 | 31 | private static Map, Integer> tokenMap = Map.of( 32 | Arrays.asList("'null'", "BooleanLiteral", "'break'", "'do'", "'instanceof'", "'typeof'", "'case'", "'else'", "'new'", "'var'", "'catch'", "'finally'", "'return'", "'void'", "'continue'", "'for'", "'switch'", "'while'", "'debugger'", "'function'", "'this'", "'with'", "'default'", "'if'", "'throw'", "'delete'", "'in'", "'try'", "'class'", "'enum'", "'extends'", "'super'", "'const'", "'export'", "'import'", "'implements'", "'let'", "'private'", "'public'", "'interface'", "'package'", "'protected'", "'static'", "'yield'"), 0xCC7832, // Orange 33 | Arrays.asList("StringLiteral", "TemplateStringLiteral"), 0x6A8759, // Green 34 | Arrays.asList("DecimalLiteral", "HexIntegerLiteral", "OctalIntegerLiteral", "OctalIntegerLiteral2", "BinaryIntegerLiteral"), 0x6897BB, // Blue 35 | Arrays.asList("0", "'['", "']'", "'('", "')'", "'{'", "'}'", "';'", "','", "'='", "'?'", "':'", "'...'", "'.'", "'++'", "'--'", "'+'", "'-'", "'~'", "'!'", "'*'", "'/'", "'%'", "'>>'", "'<<'", "'>>>'", "'<'", "'>'", "'<='", "'>='", "'=='", "'!='", "'==='", "'!=='", "'&'", "'^'", "'|'", "'&&'", "'||'", "'*='", "'/='", "'%='", "'+='", "'-='", "'<<='", "'>>='", "'>>>='", "'&='", "'^='", "'|='", "'=>'", "Identifier", "WhiteSpaces", "LineTerminator", "'\"'"), 0x000000, // Black 36 | Arrays.asList("MultiLineComment", "SingleLineComment"), 0x808080, // Gray 37 | Arrays.asList("HtmlComment", "CDataComment"), 0x666666, // Light Gray 38 | Arrays.asList("RegularExpressionLiteral"), 0x808080, // Gray (Was Dark Pink/Purple) 39 | Arrays.asList("UnexpectedCharacter"), 0x808080 // Gray (Was red) 40 | ); 41 | 42 | public Optional getColor(String tokenName) { 43 | return getColorHex(tokenName).map(Color::new); 44 | } 45 | 46 | public Optional getColorHex(String tokenName) { 47 | var optional = tokenMap.entrySet().stream().filter(entry -> entry.getKey().contains(tokenName)).findFirst().map(Map.Entry::getValue); 48 | if (optional.isEmpty()) LOGGER.error("No color found for token {}", tokenName); 49 | return optional; 50 | } 51 | 52 | public Emoji[][] highlightText(EmojiManager emojiManager, TextBlock textBlock) { 53 | var emojiGrid = textBlock.toEmoji(emojiManager); 54 | 55 | var blockText = textBlock.getText(); 56 | var lines = blockText.split("\n"); 57 | 58 | var input = CharStreams.fromString(blockText); 59 | var lex = new JavaScriptLexer(input); 60 | lex.setTokenFactory(new CommonTokenFactory(true)); 61 | var tokens = new UnbufferedTokenStream(lex); 62 | var parser = new JavaScriptParser(tokens); 63 | parser.setBuildParseTree(false); 64 | var vocabulary = parser.getVocabulary(); 65 | 66 | var i2 = 0; 67 | for (Token token; (token = tokens.get(i2)).getType() != Token.EOF; i2++, tokens.consume()) { 68 | if (token.getText().isBlank()) continue; 69 | var from = token.getCharPositionInLine(); 70 | var to = from + token.getText().length(); 71 | var tokenName = vocabulary.getDisplayName(token.getType()); 72 | var colorOptional = getColorHex(tokenName); 73 | var color = colorOptional.orElse(0x000000); 74 | 75 | var line = lines[token.getLine() - 1]; 76 | for (int i = from; i < Math.min(to, line.length()); i++) { 77 | var letter = line.charAt(i); 78 | emojiGrid[token.getLine() - 1][i] = emojiManager.getTextEmoji(COLORS.getOrDefault(color, "") + (int) letter); 79 | } 80 | } 81 | return emojiGrid; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/ide/lexer/javascript/JavaScriptBaseLexer.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.ide.lexer.javascript; 2 | 3 | import org.antlr.v4.runtime.CharStream; 4 | import org.antlr.v4.runtime.Lexer; 5 | import org.antlr.v4.runtime.Token; 6 | 7 | import java.util.Stack; 8 | 9 | /** 10 | * All lexer methods that used in grammar (IsStrictMode) 11 | * should start with Upper Case Char similar to Lexer rules. 12 | */ 13 | public abstract class JavaScriptBaseLexer extends Lexer 14 | { 15 | /** 16 | * Stores values of nested modes. By default mode is strict or 17 | * defined externally (useStrictDefault) 18 | */ 19 | private Stack scopeStrictModes = new Stack(); 20 | 21 | private Token lastToken = null; 22 | /** 23 | * Default value of strict mode 24 | * Can be defined externally by setUseStrictDefault 25 | */ 26 | private boolean useStrictDefault = false; 27 | /** 28 | * Current value of strict mode 29 | * Can be defined during parsing, see StringFunctions.js and StringGlobal.js samples 30 | */ 31 | private boolean useStrictCurrent = false; 32 | 33 | public JavaScriptBaseLexer(CharStream input) { 34 | super(input); 35 | } 36 | 37 | public boolean getStrictDefault() { 38 | return useStrictDefault; 39 | } 40 | 41 | public void setUseStrictDefault(boolean value) { 42 | useStrictDefault = value; 43 | useStrictCurrent = value; 44 | } 45 | 46 | public boolean IsSrictMode() { 47 | return useStrictCurrent; 48 | } 49 | 50 | /** 51 | * Return the next token from the character stream and records this last 52 | * token in case it resides on the default channel. This recorded token 53 | * is used to determine when the lexer could possibly match a regex 54 | * literal. Also changes scopeStrictModes stack if tokenize special 55 | * string 'use strict'; 56 | * 57 | * @return the next token from the character stream. 58 | */ 59 | @Override 60 | public Token nextToken() { 61 | Token next = super.nextToken(); 62 | 63 | if (next.getChannel() == Token.DEFAULT_CHANNEL) { 64 | // Keep track of the last token on the default channel. 65 | this.lastToken = next; 66 | } 67 | 68 | return next; 69 | } 70 | 71 | protected void ProcessOpenBrace() 72 | { 73 | useStrictCurrent = scopeStrictModes.size() > 0 && scopeStrictModes.peek() ? true : useStrictDefault; 74 | scopeStrictModes.push(useStrictCurrent); 75 | } 76 | 77 | protected void ProcessCloseBrace() 78 | { 79 | useStrictCurrent = scopeStrictModes.size() > 0 ? scopeStrictModes.pop() : useStrictDefault; 80 | } 81 | 82 | protected void ProcessStringLiteral() 83 | { 84 | if (lastToken == null || lastToken.getType() == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.OpenBrace) 85 | { 86 | String text = getText(); 87 | if (text.equals("\"use strict\"") || text.equals("'use strict'")) 88 | { 89 | if (scopeStrictModes.size() > 0) 90 | scopeStrictModes.pop(); 91 | useStrictCurrent = true; 92 | scopeStrictModes.push(useStrictCurrent); 93 | } 94 | } 95 | } 96 | 97 | /* 98 | * Returns {@code true} if the lexer can match a regex literal. 99 | */ 100 | protected boolean IsRegexPossible() { 101 | 102 | if (this.lastToken == null) { 103 | // No token has been produced yet: at the start of the input, 104 | // no division is possible, so a regex literal _is_ possible. 105 | return true; 106 | } 107 | 108 | switch (this.lastToken.getType()) { 109 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.Identifier: 110 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.NullLiteral: 111 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.BooleanLiteral: 112 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.This: 113 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.CloseBracket: 114 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.CloseParen: 115 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.OctalIntegerLiteral: 116 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.DecimalLiteral: 117 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.HexIntegerLiteral: 118 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.StringLiteral: 119 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.PlusPlus: 120 | case com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptLexer.MinusMinus: 121 | // After any of the tokens above, no regex literal can follow. 122 | return false; 123 | default: 124 | // In all other cases, a regex literal _is_ possible. 125 | return true; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/ide/lexer/javascript/JavaScriptBaseParser.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.ide.lexer.javascript; 2 | 3 | import org.antlr.v4.runtime.Lexer; 4 | import org.antlr.v4.runtime.Parser; 5 | import org.antlr.v4.runtime.Token; 6 | import org.antlr.v4.runtime.TokenStream; 7 | 8 | /** 9 | * All parser methods that used in grammar (p, prev, notLineTerminator, etc.) 10 | * should start with lower case char similar to parser rules. 11 | */ 12 | public abstract class JavaScriptBaseParser extends Parser { 13 | public JavaScriptBaseParser(TokenStream input) { 14 | super(input); 15 | } 16 | 17 | /* 18 | * Short form for prev(String str) 19 | */ 20 | protected boolean p(String str) { 21 | return prev(str); 22 | } 23 | 24 | /* 25 | * Whether the previous token value equals to @param str 26 | */ 27 | protected boolean prev(String str) { 28 | return _input.LT(-1).getText().equals(str); 29 | } 30 | 31 | /* 32 | * Short form for next(String str) 33 | */ 34 | protected boolean n(String str) { 35 | return next(str); 36 | } 37 | 38 | /* 39 | * Whether the next token value equals to @param str 40 | */ 41 | protected boolean next(String str) { 42 | return _input.LT(1).getText().equals(str); 43 | } 44 | 45 | protected boolean notLineTerminator() { 46 | return !here(com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.LineTerminator); 47 | } 48 | 49 | protected boolean notOpenBraceAndNotFunction() { 50 | int nextTokenType = _input.LT(1).getType(); 51 | return nextTokenType != com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.OpenBrace && nextTokenType != com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.Function; 52 | } 53 | 54 | protected boolean closeBrace() { 55 | return _input.LT(1).getType() == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.CloseBrace; 56 | } 57 | 58 | /* 59 | * Returns {@code true} iff on the current index of the parser's 60 | * token stream a token of the given {@code type} exists on the 61 | * {@code HIDDEN} channel. 62 | * 63 | * @param type the type of the token on the {@code HIDDEN} channel 64 | * to check. 65 | * @return {@code true} iff on the current index of the parser's 66 | * token stream a token of the given {@code type} exists on the 67 | * {@code HIDDEN} channel. 68 | */ 69 | private boolean here(final int type) { 70 | 71 | // Get the token ahead of the current index. 72 | int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; 73 | Token ahead = _input.get(possibleIndexEosToken); 74 | 75 | // Check if the token resides on the HIDDEN channel and if it's of the 76 | // provided type. 77 | return (ahead.getChannel() == Lexer.HIDDEN) && (ahead.getType() == type); 78 | } 79 | 80 | /* 81 | * Returns {@code true} iff on the current index of the parser's 82 | * token stream a token exists on the {@code HIDDEN} channel which 83 | * either is a line terminator, or is a multi line comment that 84 | * contains a line terminator. 85 | * 86 | * @return {@code true} iff on the current index of the parser's 87 | * token stream a token exists on the {@code HIDDEN} channel which 88 | * either is a line terminator, or is a multi line comment that 89 | * contains a line terminator. 90 | */ 91 | protected boolean lineTerminatorAhead() { 92 | 93 | // Get the token ahead of the current index. 94 | int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; 95 | Token ahead = _input.get(possibleIndexEosToken); 96 | 97 | if (ahead.getChannel() != Lexer.HIDDEN) { 98 | // We're only interested in tokens on the HIDDEN channel. 99 | return false; 100 | } 101 | 102 | if (ahead.getType() == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.LineTerminator) { 103 | // There is definitely a line terminator ahead. 104 | return true; 105 | } 106 | 107 | if (ahead.getType() == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.WhiteSpaces) { 108 | // Get the token ahead of the current whitespaces. 109 | possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 2; 110 | ahead = _input.get(possibleIndexEosToken); 111 | } 112 | 113 | // Get the token's text and type. 114 | String text = ahead.getText(); 115 | int type = ahead.getType(); 116 | 117 | // Check if the token is, or contains a line terminator. 118 | return (type == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.MultiLineComment && (text.contains("\r") || text.contains("\n"))) || 119 | (type == com.uddernetworks.emojide.ide.lexer.javascript.JavaScriptParser.LineTerminator); 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/keyboard/KeyPressEvent.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.keyboard; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.StaticEmoji; 4 | import com.uddernetworks.emojide.event.Cancellable; 5 | 6 | /** 7 | * An event to store data about a key being pressed via the {@link SimpleKeyboardInputManager}. 8 | */ 9 | public class KeyPressEvent extends Cancellable { 10 | private boolean alphanumeric; 11 | private char character; 12 | private StaticEmoji staticEmoji; 13 | 14 | /** 15 | * Creates a {@link KeyPressEvent} if a {@link StaticEmoji} was pressed. 16 | * 17 | * @param staticEmoji The pressed {@link StaticEmoji} 18 | */ 19 | public KeyPressEvent(StaticEmoji staticEmoji) { 20 | this.staticEmoji = staticEmoji; 21 | } 22 | 23 | /** 24 | * Creates a {@link KeyPressEvent} if a character was pressed. 25 | * 26 | * @param character The pressed character 27 | */ 28 | public KeyPressEvent(char character) { 29 | this.alphanumeric = true; 30 | this.character = character; 31 | } 32 | 33 | /** 34 | * Gets if a character was pressed. 35 | * 36 | * @return If a character was pressed 37 | */ 38 | public boolean isAlphanumeric() { 39 | return alphanumeric; 40 | } 41 | 42 | /** 43 | * Gets the character pressed, if any. 44 | * 45 | * @return The character pressed 46 | */ 47 | public char getCharacter() { 48 | return character; 49 | } 50 | 51 | /** 52 | * Gets the {@link StaticEmoji} pressed, if any. 53 | * 54 | * @return The {@link StaticEmoji} pressed 55 | */ 56 | public StaticEmoji getStaticEmoji() { 57 | return staticEmoji; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return alphanumeric ? String.valueOf(character) : staticEmoji.getName(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/keyboard/KeyboardInputManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.keyboard; 2 | 3 | import com.uddernetworks.emojide.discord.emoji.Emoji; 4 | import com.uddernetworks.emojide.web.WebListener; 5 | import net.dv8tion.jda.api.entities.TextChannel; 6 | import net.dv8tion.jda.api.hooks.EventListener; 7 | 8 | import java.util.Optional; 9 | 10 | public interface KeyboardInputManager extends EventListener { 11 | 12 | /** 13 | * Creates a keyboard in the given {@link TextChannel}. The keyboard works by making an embed with two inline fields 14 | * next to each other in each row, with 8 emojis per row, normal emojis being padded by :discord: to meet this 15 | * criteria. This is to make each row look uniform. Each emoji is a link to the webserver hosted by this bot, which 16 | * is why each field is only half of the embed width, as there is a low character count for fields, and even lower 17 | * for the full description. 18 | *

19 | * When an emoji's link is clicked, the page opened sends another request to the webserver with a random number that 20 | * then counts the request. If this random link has not been registered yet, events are fired. This is to prevent 21 | * double presses from browsers sending things twice. 22 | * 23 | * @param textChannel The {@link TextChannel} to send the keyboard in 24 | */ 25 | void createKeyboard(TextChannel textChannel); 26 | 27 | /** 28 | * Changes the keyboard to uppercase letters. 29 | */ 30 | void changeToUpper(); 31 | 32 | /** 33 | * Changes the keyboard to lowercase letters. 34 | */ 35 | void changeToLower(); 36 | 37 | /** 38 | * Removes the keyboard message. 39 | */ 40 | void removeKeyboard(); 41 | 42 | /** 43 | * Gets a pair of {@link Emoji}s in the keyboard, e.g. the left and right emojis for the shift key. 44 | * 45 | * @param emoji An {@link Emoji} from a pair 46 | * @return The {@link Pair} name of the emoji 47 | */ 48 | Optional getPair(Emoji emoji); 49 | 50 | /** 51 | * Gets the state of the keyboard, i.e. generally what key is pressed down to modify future keypresses. 52 | * 53 | * @return The {@link ActiveState} of the keyboard. 54 | */ 55 | ActiveState getState(); 56 | 57 | /** 58 | * Invoked by the {@link WebListener}, and is in charge of raising events. 59 | * 60 | * @param key The key clicked, in the format by the {@link WebListener} 61 | */ 62 | void handleKey(String key); 63 | 64 | enum Pair { 65 | SPACE, SHIFT, ENTER, CAPS, CTRL, ALT, FN 66 | } 67 | 68 | enum ActiveState { 69 | NONE, SHIFT, CTRL, ALT, FN 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/keyboard/KeyboardRaisable.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.keyboard; 2 | 3 | import com.uddernetworks.emojide.event.EventRaiser; 4 | import com.uddernetworks.emojide.event.Raisable; 5 | 6 | public class KeyboardRaisable extends Raisable { 7 | public KeyboardRaisable() { 8 | super("keyboard", KeyPressEvent.class); 9 | } 10 | 11 | public static KeyboardRaisable get() { 12 | return (KeyboardRaisable) EventRaiser.getRaisable("keyboard"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/main/ChoosableEnum.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.main; 2 | 3 | public interface ChoosableEnum { 4 | int ordinal(); 5 | String getName(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/main/ConfigManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.main; 2 | 3 | import com.electronwill.nightconfig.core.file.FileConfig; 4 | 5 | import java.util.List; 6 | 7 | public interface ConfigManager { 8 | 9 | /** 10 | * Initializes all config fields. 11 | */ 12 | void init(); 13 | 14 | /** 15 | * Gets the primary EmojIDE bot token to use. 16 | * 17 | * @return The token 18 | */ 19 | String getPrimaryToken(); 20 | 21 | /** 22 | * Gets a list of servers the bot is in and has admin access to, to upload emojis. 23 | * 24 | * @return A list of server IDs 25 | */ 26 | List getServers(); 27 | 28 | /** 29 | * Gets the {@link FileConfig}. 30 | * 31 | * @return The {@link FileConfig} 32 | */ 33 | FileConfig getConfig(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/main/CustomPool.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.main; 2 | 3 | import net.dv8tion.jda.api.events.http.HttpRequestEvent; 4 | import net.dv8tion.jda.internal.requests.ratelimit.IBucket; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.concurrent.ScheduledFuture; 10 | import java.util.concurrent.ScheduledThreadPoolExecutor; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import static java.util.concurrent.TimeUnit.SECONDS; 14 | 15 | public class CustomPool extends ScheduledThreadPoolExecutor { 16 | 17 | private static Logger LOGGER = LoggerFactory.getLogger(CustomPool.class); 18 | 19 | private static final Runnable EMPTY = () -> {}; 20 | private EmojIDE emojIDE; 21 | public static boolean start = false; 22 | 23 | public CustomPool(EmojIDE emojIDE) { 24 | super(8); 25 | this.emojIDE = emojIDE; 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { 31 | if (!(command instanceof IBucket)) return super.schedule(command, delay, unit); 32 | if (unit.toMillis(delay) >= SECONDS.toMillis(10)) { 33 | LOGGER.info("Long delay ({}ms) so cancelling...", unit.toMillis(delay)); 34 | 35 | var rateLimiter = emojIDE.getJda().getRequester().getRateLimiter(); 36 | // rateLimiter.getRouteBuckets() 37 | var bucket = (IBucket) command; 38 | bucket.getRequests().clear(); 39 | 40 | return super.schedule(() -> { 41 | LOGGER.info("Running after 100ms"); 42 | command.run(); 43 | LOGGER.info("Done!"); 44 | }, 100, TimeUnit.MILLISECONDS); 45 | } 46 | return super.schedule(command, delay, unit); 47 | } 48 | 49 | private ScheduledFuture empty() { 50 | return super.schedule(EMPTY, 0, TimeUnit.MILLISECONDS); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/main/DefaultConfigManager.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.main; 2 | 3 | import com.electronwill.nightconfig.core.file.CommentedFileConfig; 4 | import com.electronwill.nightconfig.core.file.FileConfig; 5 | 6 | import java.util.List; 7 | 8 | public class DefaultConfigManager implements ConfigManager { 9 | 10 | private String fileName; 11 | 12 | private String primaryToken; 13 | private List servers; 14 | private FileConfig config; 15 | 16 | public DefaultConfigManager(String fileName) { 17 | this.fileName = fileName; 18 | } 19 | 20 | @Override 21 | public void init() { 22 | config = CommentedFileConfig.builder(this.fileName).autosave().build(); 23 | config.load(); 24 | primaryToken = config.get("bots.primary-token"); 25 | servers = config.get("bots.servers"); 26 | } 27 | 28 | @Override 29 | public String getPrimaryToken() { 30 | return primaryToken; 31 | } 32 | 33 | @Override 34 | public List getServers() { 35 | return servers; 36 | } 37 | 38 | @Override 39 | public FileConfig getConfig() { 40 | return config; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/main/Thread.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.main; 2 | 3 | public class Thread { 4 | public static void sleep(long milliseconds) { 5 | try { 6 | java.lang.Thread.sleep(milliseconds); 7 | } catch (InterruptedException ignored) {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/utils/Commandline.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Scanner; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.function.Consumer; 15 | 16 | // Stolen from MS Paint IDE 17 | // https://github.com/MSPaintIDE/MSPaintIDE/blob/1e8697b37e4595a120cd3009517d966e8797ea1f/src/main/java/com/uddernetworks/mspaint/cmd/Commandline.java 18 | public class Commandline { 19 | 20 | private static Logger LOGGER = LoggerFactory.getLogger(Commandline.class); 21 | 22 | public static String runCommand(List command) { 23 | return runCommand(false, command); 24 | } 25 | 26 | public static String runCommand(String... command) { 27 | return runCommand(false, Arrays.asList(command)); 28 | } 29 | 30 | public static String runCommand(boolean cmdCPrefix, String... command) { 31 | return runCommand(cmdCPrefix, Arrays.asList(command)); 32 | } 33 | 34 | public static String runCommand(boolean cmdCPrefix, List command) { 35 | if (cmdCPrefix) { 36 | var temp = new ArrayList<>(Arrays.asList("cmd", "/c")); 37 | temp.addAll(command); 38 | command = temp; 39 | } 40 | var result = new StringBuilder(); 41 | runInheritedCommand(command, null, false, process -> { 42 | inheritIOToStringBuilder(process.getInputStream(), result); 43 | inheritIOToStringBuilder(process.getErrorStream(), result); 44 | }); 45 | return result.toString(); 46 | } 47 | 48 | public static int runLiveCommand(List command) { 49 | return runLiveCommand(command, null); 50 | } 51 | 52 | public static int runLiveCommand(List command, String threadName) { 53 | return runLiveCommand(command, null, threadName); 54 | } 55 | 56 | public static int runLiveCommand(List command, File directory, String threadName) { 57 | return runInheritedCommand(command, directory, true, process -> { 58 | inheritIO(process.getInputStream(), threadName); 59 | inheritIO(process.getErrorStream(), threadName); 60 | }); 61 | } 62 | 63 | public static int runInheritedCommand(List command, File directory, boolean printStatus, Consumer processCreate) { 64 | LOGGER.info("Running command {}", String.join(" ", command)); 65 | try { 66 | ProcessBuilder processBuilder = new ProcessBuilder(command); 67 | if (directory != null) processBuilder.directory(directory); 68 | Process process = processBuilder.start(); 69 | 70 | processCreate.accept(process); 71 | 72 | var exitCode = 1; 73 | Runtime.getRuntime().addShutdownHook(new Thread(process::destroyForcibly)); 74 | 75 | try { 76 | exitCode = process.waitFor(); 77 | } catch (InterruptedException ignored) { // This is probably from manually stopping the process; nothing bad to report 78 | process.destroyForcibly(); 79 | } 80 | 81 | if (printStatus) LOGGER.info("Process terminated with {}", exitCode); 82 | return exitCode; 83 | } catch (IOException e) { 84 | if (!e.getLocalizedMessage().contains("The system cannot find the file specified")) LOGGER.error("An error occurred while running command with arguments " + command, e); 85 | return -1; 86 | } 87 | } 88 | 89 | private static void inheritIO(InputStream inputStream, String threadName) { 90 | CompletableFuture.runAsync(() -> { 91 | Thread.currentThread().setName(threadName); 92 | Scanner sc = new Scanner(inputStream); 93 | while (sc.hasNextLine()) { 94 | LOGGER.info(sc.nextLine()); 95 | } 96 | }); 97 | } 98 | 99 | private static void inheritIOToStringBuilder(InputStream inputStream, StringBuilder stringBuilder) { 100 | CompletableFuture.runAsync(() -> { 101 | Scanner sc = new Scanner(inputStream); 102 | while (sc.hasNextLine()) { 103 | var line = sc.nextLine(); 104 | synchronized (stringBuilder) { 105 | stringBuilder.append(line); 106 | } 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/BasicWebCallback.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.function.Consumer; 6 | 7 | public class BasicWebCallback implements WebCallback { 8 | 9 | private String name; 10 | private List required; 11 | private Consumer> onReceive; 12 | 13 | public BasicWebCallback(String name, List required, Consumer> onReceive) { 14 | this.name = name; 15 | this.required = required; 16 | this.onReceive = onReceive; 17 | } 18 | 19 | @Override 20 | public void receive(Map query) { 21 | onReceive.accept(query); 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | @Override 30 | public List getRequired() { 31 | return required; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/BasicWebCallbackHandler.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import com.uddernetworks.emojide.main.EmojIDE; 4 | import net.dv8tion.jda.api.JDA; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.*; 9 | import java.util.function.Consumer; 10 | import java.util.stream.Collectors; 11 | 12 | public class BasicWebCallbackHandler implements WebCallbackHandler { 13 | 14 | private static Logger LOGGER = LoggerFactory.getLogger(BasicWebCallbackHandler.class); 15 | 16 | private EmojIDE emojIDE; 17 | private JDA jda; 18 | private List callbacks = new ArrayList<>(); 19 | 20 | public BasicWebCallbackHandler(EmojIDE emojIDE) { 21 | this.emojIDE = emojIDE; 22 | this.jda = emojIDE.getJda(); 23 | } 24 | 25 | @Override 26 | public void registerCallback(String name, List requiredParams, Consumer> onReceive) { 27 | callbacks.add(new BasicWebCallback(name, requiredParams, onReceive)); 28 | } 29 | 30 | @Override 31 | public void registerCommandCallback(String name, CommandCallback commandCallback) { 32 | registerCommandCallback(name, Collections.emptyList(), commandCallback); 33 | } 34 | 35 | @Override 36 | public void registerCommandCallback(String name, List requiredParams, CommandCallback commandCallback) { 37 | var commandRequired = new ArrayList<>(Arrays.asList("channel", "member")); 38 | var allRequired = new ArrayList<>(requiredParams); 39 | allRequired.addAll(commandRequired); 40 | registerCallback(name, allRequired, query -> { 41 | var channel = jda.getTextChannelById(query.get("channel")); 42 | if (channel == null) return; 43 | var member = channel.getGuild().getMemberById(query.get("member")); 44 | if (member == null) return; 45 | commandRequired.forEach(query::remove); 46 | commandCallback.accept(member, channel, query); 47 | }); 48 | } 49 | 50 | @Override 51 | public String generateMdLink(String text, String name, Map query) { 52 | return "[" + text + "](" + generateLink(name, query) + ")"; 53 | } 54 | 55 | @Override 56 | public String generateLink(String name, Map query) { 57 | var base = new StringBuilder(EmojIDE.getConfigManager().getConfig().get("web.host") + "/c/" + name); 58 | if (query.isEmpty()) return base.toString(); 59 | base.append("?"); 60 | query.forEach((key, value) -> base.append(key).append("=").append(value).append("&")); 61 | return base.substring(0, base.length() - 1); 62 | } 63 | 64 | @Override 65 | public void handleCallback(String url, Map query) { 66 | var subTo = url.indexOf("?"); 67 | subTo = subTo == -1 ? url.length() : subTo; 68 | url = url.substring(3, subTo); 69 | 70 | String finalUrl = url; 71 | callbacks.stream().filter(callback -> callback.getName().equalsIgnoreCase(finalUrl)).findFirst().ifPresent(callback -> { 72 | var gotKeys = new ArrayList<>(query.keySet()); 73 | gotKeys.retainAll(callback.getRequired()); 74 | if (gotKeys.size() != callback.getRequired().size()) { 75 | LOGGER.error("Callback did not receive the correct query parameters."); 76 | return; 77 | } 78 | 79 | callback.receive(gotKeys.stream().collect(Collectors.toMap(key -> key, query::get))); 80 | }); 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/CommandCallback.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import net.dv8tion.jda.api.entities.Member; 4 | import net.dv8tion.jda.api.entities.TextChannel; 5 | 6 | import java.util.Map; 7 | 8 | @FunctionalInterface 9 | public interface CommandCallback { 10 | void accept(Member member, TextChannel channel, Map query); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/WebCallback.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface WebCallback { 7 | 8 | /** 9 | * When the callback is received via a web request. 10 | * 11 | * @param query The web query 12 | */ 13 | void receive(Map query); 14 | 15 | /** 16 | * The unique name of the callback. 17 | * 18 | * @return The name 19 | */ 20 | String getName(); 21 | 22 | /** 23 | * Gets the required query parameters. If not all parameters are found in a request, the callback is not invoked. 24 | * 25 | * @return The required parameters 26 | */ 27 | List getRequired(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/WebCallbackHandler.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.function.Consumer; 6 | 7 | public interface WebCallbackHandler { 8 | 9 | /** 10 | * Registers a callback with the given name and required parameters. 11 | * 12 | * @param name The unique name of the callback 13 | * @param requiredParams The query parameters that must be available for the callback to invoke 14 | * @param onReceive When a callback is invoked 15 | */ 16 | void registerCallback(String name, List requiredParams, Consumer> onReceive); 17 | 18 | /** 19 | * Registers a callback with the intended use being for commands, as the `channel` id and `member` id are parsed and 20 | * validated for the callback to be invoked. 21 | * 22 | * @param name The unique name of the callback 23 | * @param commandCallback The callback to be invoked 24 | */ 25 | void registerCommandCallback(String name, CommandCallback commandCallback); 26 | 27 | /** 28 | * Registers a callback with the intended use being for commands, as the `channel` id and `member` id are parsed and 29 | * validated for the callback to be invoked. This allows for other required parameters. 30 | * 31 | * @param name The unique name of the callback 32 | * @param requiredParams Extra required parameters for the callback 33 | * @param commandCallback The callback to be invoked 34 | */ 35 | void registerCommandCallback(String name, List requiredParams, CommandCallback commandCallback); 36 | 37 | /** 38 | * Generates a link to be placed directly in markdown that references a callback name with query parameters. 39 | * 40 | * @param text The text to display in the link 41 | * @param name The name of the callback 42 | * @param query The query parameters of the link 43 | * @return The markdown of the link 44 | */ 45 | String generateMdLink(String text, String name, Map query); 46 | 47 | /** 48 | * Generates a raw link referencing a callback name with query parameters. 49 | * 50 | * @param name The name of the callback 51 | * @param query The query parameters of the callback 52 | * @return The raw link 53 | */ 54 | String generateLink(String name, Map query); 55 | 56 | /** 57 | * Invoked by the webserver when a request for a callback is made. This parses and finds appropriate listeners with 58 | * the correct query parameters. 59 | * 60 | * @param url The ending URL without the prefix, e.g. if http://localhost:6969/z/info?one=two is requested, this 61 | * parameter will be info?one=two 62 | * @param query The query parameters of the request 63 | */ 64 | void handleCallback(String url, Map query); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/uddernetworks/emojide/web/WebListener.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.web; 2 | 3 | import com.uddernetworks.emojide.keyboard.KeyboardInputManager; 4 | import com.uddernetworks.emojide.keyboard.SimpleKeyboardInputManager; 5 | import simplenet.Client; 6 | 7 | import java.util.Map; 8 | import java.util.function.BiConsumer; 9 | import java.util.function.BiFunction; 10 | 11 | public interface WebListener { 12 | 13 | /** 14 | * Starts the web listener's server, by default on port 80. 15 | * 16 | * @param keyboardInputManager The {@link SimpleKeyboardInputManager} to send input data to. 17 | */ 18 | void start(KeyboardInputManager keyboardInputManager); 19 | 20 | /** 21 | * Invokes {@link #parseHeaders(Client, BiConsumer)} and if the requestHeaderFunction returns false, an error 22 | * response is sent. Either way, the {@link Client} given is closed at the end. 23 | * 24 | * @param client The {@link Client} 25 | * @param requestHeaderFunction The function giving request data and header data, returning if the request was 26 | * successful. 27 | */ 28 | void tryAndParse(Client client, BiFunction, Boolean> requestHeaderFunction); 29 | 30 | /** 31 | * Parses the headers from the given {@link Client}, invoking the function by default when the Connection header is 32 | * given. 33 | * 34 | * @param client The {@link Client} 35 | * @param headersComplete The request data and headers 36 | */ 37 | void parseHeaders(Client client, BiConsumer> headersComplete); 38 | 39 | /** 40 | * Generated a request in bytes to be sent from the given body. 41 | * 42 | * @param body The body to include in the request 43 | * @return The request, in bytes, to send 44 | */ 45 | byte[] generateRequest(String body); 46 | 47 | /** 48 | * Creates default headers for {@link #generateRequest(String)}. By default, this is: 49 | *

50 |      *     HTTP/1.1 200 OK
51 |      *     Server: EmojIDE
52 |      *     Content-Length: [dynamic]
53 |      *     Content-Type: text/html
54 |      *     Access-Control-Allow-Origin: *
55 |      *     Connection: Closed
56 |      *
57 |      * 
58 | * 59 | * @return The headers 60 | */ 61 | String createHeaders(); 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/config.conf: -------------------------------------------------------------------------------- 1 | web { 2 | host: localhost 3 | port: 80 4 | } 5 | bots { 6 | primary-token: "" 7 | servers: [] 8 | } 9 | ide { 10 | channel: 0 11 | font: 0 12 | theme: 1 13 | } 14 | show-important: 0 15 | -------------------------------------------------------------------------------- /src/main/resources/createDocument.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `documents` (name, author, content) VALUES (?, ?, ?); -------------------------------------------------------------------------------- /src/main/resources/documents.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `documents` ( 2 | name varchar(64) UNIQUE, 3 | author bigint NOT NULL, 4 | content longtext NOT NULL 5 | ); -------------------------------------------------------------------------------- /src/main/resources/getDocument.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM `documents` WHERE name = ?; -------------------------------------------------------------------------------- /src/main/resources/getDocuments.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM `documents`; -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=[%d{HH:mm:ss}] [%t/%p]: %m%n -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/modifyDocument.sql: -------------------------------------------------------------------------------- 1 | UPDATE `documents` SET content = ? WHERE name = ?; -------------------------------------------------------------------------------- /src/main/resources/removeDocument.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM `documents` WHERE name = ?; -------------------------------------------------------------------------------- /src/test/java/com/uddernetworks/emojide/gui/text/AutoGrowArrayListTest.java: -------------------------------------------------------------------------------- 1 | package com.uddernetworks.emojide.gui.text; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class AutoGrowArrayListTest { 12 | 13 | @Test 14 | public void getAddingToGrow() { 15 | var list = new AutoGrowArrayList(); 16 | 17 | for (int i = 0; i < 1000; i++) { 18 | int finalI = i; 19 | list.setAddingToGrow(() -> finalI); 20 | assertEquals(i, list.getAddingToGrow().get()); 21 | } 22 | } 23 | 24 | @Test 25 | public void get() { 26 | var list = new AutoGrowArrayList(); 27 | list.add(rand()); 28 | 29 | for (int i = 1; i < 1000; i++) { 30 | assertNull(list.get(i)); 31 | } 32 | } 33 | 34 | @Test 35 | public void set() { 36 | var list = new AutoGrowArrayList(); 37 | list.set(2, rand()); 38 | assertNull(list.get(0)); 39 | assertNull(list.get(1)); 40 | assertNotNull(list.get(2)); 41 | assertNull(list.get(3)); 42 | } 43 | 44 | @Test 45 | public void add() { 46 | var list = new AutoGrowArrayList(); 47 | list.add(1); 48 | list.add(2); 49 | list.add(3); 50 | list.add(1, 999); 51 | 52 | assertTrue(listsEquals(list, 1, 999, 2, 3)); 53 | } 54 | 55 | @Test 56 | public void addAll() { 57 | var list = new AutoGrowArrayList(); 58 | list.add(0); 59 | list.add(1); 60 | list.add(2); 61 | 62 | list.addAll(1, Arrays.asList(10, 11, 12)); 63 | 64 | assertTrue(listsEquals(list, 0, 10, 11, 12, 1, 2)); 65 | } 66 | 67 | private boolean listsEquals(List list1, T... elements) { 68 | return listsEquals(list1, Arrays.asList(elements)); 69 | } 70 | 71 | private boolean listsEquals(List list1, List list2) { 72 | if (list1.size() != list2.size()) return false; 73 | if (list1 == list2) return true; 74 | for (int i = 0; i < list1.size(); i++) { 75 | if (!list1.get(i).equals(list2.get(i))) return false; 76 | } 77 | return true; 78 | } 79 | 80 | private int rand() { 81 | return ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /welcome_emojis/name/0w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/0w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/0w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/0w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/1w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/1w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/1w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/1w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/2w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/2w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/2w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/2w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/3w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/3w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/3w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/3w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/4w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/4w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/4w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/4w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/5w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/5w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/5w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/5w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/6w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/6w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/6w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/6w1.png -------------------------------------------------------------------------------- /welcome_emojis/name/7w0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/7w0.png -------------------------------------------------------------------------------- /welcome_emojis/name/7w1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubbaBoy/EmojIDE/0d8595934e428e6bbb5ba7ace0772d21099a2b23/welcome_emojis/name/7w1.png --------------------------------------------------------------------------------