├── .editorconfig
├── .idea
├── codeStyles
│ └── codeStyleConfig.xml
├── vcs.xml
├── discord.xml
├── .gitignore
├── artifacts
│ └── TAS_Editor.xml
├── compiler.xml
├── misc.xml
├── jarRepositories.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── src
└── main
│ └── java
│ └── io
│ └── github
│ └── jadefalke2
│ ├── actions
│ ├── Action.java
│ ├── InsertEmptyLineAction.java
│ ├── ButtonAction.java
│ ├── StickAction.java
│ └── LineAction.java
│ ├── util
│ ├── ScriptObserver.java
│ ├── TriFunction.java
│ ├── Logger.java
│ ├── SimpleDocumentListener.java
│ ├── Button.java
│ ├── CorruptedScriptException.java
│ ├── CustomPianoRollCellRenderer.java
│ ├── ObservableProperty.java
│ ├── ScriptTableModel.java
│ ├── Util.java
│ ├── ByteConversions.java
│ ├── Settings.java
│ ├── InputDrawMouseListener.java
│ └── ByteDataStream.java
│ ├── stickRelatedClasses
│ ├── CustomChangeListener.java
│ ├── ChangeObject.java
│ ├── StickPosition.java
│ ├── SmoothTransitionDialog.java
│ ├── JoystickPanel.java
│ └── Joystick.java
│ ├── script
│ ├── Format.java
│ ├── NXTas.java
│ └── STas.java
│ ├── TAS.java
│ ├── components
│ ├── ColumnRightClickMenu.java
│ ├── AddLinesDialog.java
│ ├── TasFileChooser.java
│ ├── LineRightClickMenu.java
│ ├── TabbedScriptsPane.java
│ ├── SelectLinesDialog.java
│ ├── MainEditorWindow.java
│ ├── ScriptTab.java
│ ├── SideJoystickPanel.java
│ ├── SettingsDialog.java
│ ├── PianoRoll.java
│ └── MainJMenuBar.java
│ ├── InputLine.java
│ └── Script.java
├── README.md
├── LICENSE
├── .gitignore
├── .github
└── workflows
│ └── main.yml
├── dependency-reduced-pom.xml
└── pom.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | insert_final_newline = true
5 | end_of_line = lf
6 |
7 | [*.java]
8 | indent_style = tab
9 | indent_size = 4
10 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /../../../../../:\New Projects\Privat\TAS editor\.idea/dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/actions/Action.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.actions;
2 |
3 | /**
4 | * Represents a user action that gets added to the undo history and is able to be reverted
5 | */
6 | public interface Action {
7 |
8 | void execute();
9 |
10 | void revert();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/ScriptObserver.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import java.io.File;
4 |
5 | public interface ScriptObserver {
6 |
7 | default void onFileChange(File file) {}
8 | default void onLengthChange(int length) {}
9 | default void onDirtyChange(boolean dirty) {}
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/stickRelatedClasses/CustomChangeListener.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.stickRelatedClasses;
2 |
3 | import java.util.EventListener;
4 |
5 | //custom version of event.ChangeListener to use ChangeObject
6 | public interface CustomChangeListener extends EventListener {
7 | void stateChanged(ChangeObject e);
8 | default void silentStateChanged(ChangeObject e) {}
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/stickRelatedClasses/ChangeObject.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.stickRelatedClasses;
2 |
3 | import javax.swing.event.ChangeEvent;
4 |
5 | public class ChangeObject extends ChangeEvent {
6 |
7 | private final E value;
8 |
9 | public ChangeObject(E value, Object source){
10 | super(source);
11 | this.value = value;
12 | }
13 |
14 | public E getValue(){
15 | return value;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/.idea/artifacts/TAS_Editor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/out/artifacts/TAS_Editor
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/TriFunction.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import java.util.Objects;
4 | import java.util.function.Function;
5 |
6 | @FunctionalInterface
7 | public
8 | interface TriFunction {
9 |
10 | R apply(A a, B b, C c);
11 |
12 | default TriFunction andThen(
13 | Function super R, ? extends V> after) {
14 | Objects.requireNonNull(after);
15 | return (A a, B b, C c) -> after.apply(apply(a, b, c));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/Logger.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import java.time.LocalDateTime;
4 | import java.time.format.DateTimeFormatter;
5 |
6 | public class Logger {
7 |
8 | public static void log (String s) {
9 | // using this to make it easier to later change this behaviour
10 | System.out.println(getTimeString() + s);
11 | }
12 |
13 | private static String getTimeString () {
14 | DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss.ms");
15 | LocalDateTime now = LocalDateTime.now();
16 | return "[" + dtf.format(now) + "]: ";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/SimpleDocumentListener.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import javax.swing.event.DocumentEvent;
4 | import javax.swing.event.DocumentListener;
5 |
6 | @FunctionalInterface
7 | public interface SimpleDocumentListener extends DocumentListener {
8 | void update(DocumentEvent e);
9 |
10 | @Override
11 | default void insertUpdate(DocumentEvent e) {
12 | update(e);
13 | }
14 | @Override
15 | default void removeUpdate(DocumentEvent e) {
16 | update(e);
17 | }
18 | @Override
19 | default void changedUpdate(DocumentEvent e) {
20 | update(e);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/Button.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | //TODO still not happy with this, as it handles both frontend and backend...
4 | //toString used for displaying, name for script files
5 |
6 | public enum Button {
7 | KEY_A,
8 | KEY_B,
9 | KEY_X,
10 | KEY_Y,
11 |
12 | KEY_ZR,
13 | KEY_ZL,
14 |
15 | KEY_R,
16 | KEY_L,
17 |
18 | KEY_PLUS,
19 | KEY_MINUS,
20 |
21 | KEY_DLEFT,
22 | KEY_DRIGHT,
23 | KEY_DUP,
24 | KEY_DDOWN,
25 |
26 | KEY_LSTICK,
27 | KEY_RSTICK;
28 |
29 | @Override
30 | public String toString(){
31 | return name().replace("KEY_","");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/CorruptedScriptException.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import javax.swing.JOptionPane;
4 |
5 | public class CorruptedScriptException extends Exception {
6 |
7 | public CorruptedScriptException (String errorMessage, int frame, Throwable cause) {
8 | super(errorMessage + (frame == -1 ? "" : " \nat frame " + frame), cause);
9 |
10 | Logger.log(errorMessage);
11 | String fullErrorMessage = errorMessage + " \nPlease check your script manually " + (frame == -1 ? "" : " \nat frame " + frame);
12 | JOptionPane.showMessageDialog(null, fullErrorMessage, "An exception occured", JOptionPane.ERROR_MESSAGE);
13 | }
14 | public CorruptedScriptException (String errorMessage, int frame) {
15 | this(errorMessage, frame, null);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/CustomPianoRollCellRenderer.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import javax.swing.JTable;
4 | import javax.swing.SwingConstants;
5 | import javax.swing.table.DefaultTableCellRenderer;
6 | import java.awt.Component;
7 |
8 | public class CustomPianoRollCellRenderer extends DefaultTableCellRenderer {
9 |
10 | public CustomPianoRollCellRenderer() {
11 | super();
12 | setHorizontalAlignment(SwingConstants.CENTER);
13 | }
14 |
15 | @Override
16 | public Component getTableCellRendererComponent(
17 | JTable table, Object value, boolean isSelected,
18 | boolean hasFocus, int row, int column) {
19 | // wrap in HTML to enable HTML rendering, which disables ellipsis when text is too long
20 | return super.getTableCellRendererComponent(
21 | table, ""+value+"", isSelected, hasFocus, row, column);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/script/Format.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.script;
2 |
3 | import io.github.jadefalke2.Script;
4 | import io.github.jadefalke2.util.CorruptedScriptException;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | public enum Format {
10 |
11 | STAS,
12 | nxTAS;
13 |
14 | public static Script read(File file, Format format) throws IOException, CorruptedScriptException {
15 | if(format == nxTAS) return NXTas.read(file);
16 | else if(format == STAS) return STas.read(file);
17 | else throw new IllegalStateException("Unexpected value: " + format);
18 | }
19 | public static void write(Script script, File file, Format format) throws IOException {
20 | if(format == nxTAS) NXTas.write(script, file);
21 | else if(format == STAS) STas.write(script, file);
22 | else throw new IllegalStateException("Unexpected value: " + format);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TAS-Editor
2 |
3 | A simple-to-use GUI for Nintendo Switch TAS editing.
4 |
5 | # Set-up
6 |
7 | *Do not download the version from this repo. Use the [TAS-Editor-Launcher](https://github.com/MonsterDruide1/TAS-Editor-Launcher/releases/latest) release which has all the information on how to set it up.*
8 |
9 | # Usage
10 | The program itself should be self-explanatory. To see all possible ways of interacting with this program, please refer to the [wiki](https://github.com/MonsterDruide1/TAS-Editor/wiki), where you can read for example about how to [select frames](https://github.com/MonsterDruide1/TAS-Editor/wiki/Change-Stick-Input) or [change stick inputs](https://github.com/MonsterDruide1/TAS-Editor/wiki/Selecting-frames).
11 |
12 | # Support
13 | If you need help with anything, have questions or suggestions, you can contact me (MonsterDruide1) either on the [Super Mario Odyssey TAS Discord](https://discord.gg/YMJ9Njzvcd), or using the [Issues](https://github.com/MonsterDruide1/TAS-Editor/issues).
14 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/actions/InsertEmptyLineAction.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.actions;
2 |
3 | import io.github.jadefalke2.InputLine;
4 | import io.github.jadefalke2.Script;
5 |
6 | public class InsertEmptyLineAction implements Action {
7 |
8 | private final Script script;
9 | private final int position;
10 | private final int amount;
11 |
12 | public InsertEmptyLineAction(Script script, int position, int amount) {
13 | this.script = script;
14 | this.position = position;
15 | this.amount = amount;
16 | }
17 |
18 | @Override
19 | public void execute() {
20 | for (int i = 0; i < amount; i++){
21 | int actualIndex = position + i;
22 | script.insertRow(actualIndex, InputLine.getEmpty());
23 | }
24 | }
25 |
26 | @Override
27 | public void revert() {
28 | for (int i = 0; i < amount; i++){
29 | script.removeRow(position);
30 | }
31 | }
32 |
33 | @Override
34 | public String toString() {
35 | return "InsertEmptyLineAction for "+amount+" frames at frame "+position;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/ObservableProperty.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class ObservableProperty {
7 |
8 | @FunctionalInterface
9 | public interface PropertyChangeListener {
10 | default void onChange(T newValue, T oldValue) { onChange(newValue); }
11 | void onChange(T newValue);
12 | }
13 |
14 | private T value;
15 | private final List> listeners;
16 |
17 | public ObservableProperty(T value) {
18 | this.value = value;
19 | listeners = new ArrayList<>();
20 | }
21 |
22 | public void set(T value) {
23 | if(value.equals(this.value)) return;
24 |
25 | this.value = value;
26 | for(PropertyChangeListener listener : listeners) {
27 | listener.onChange(value, this.value);
28 | }
29 | }
30 | public T get() {
31 | return value;
32 | }
33 |
34 | public void attachListener(PropertyChangeListener listener) {
35 | listeners.add(listener);
36 | }
37 | public void detachListener(PropertyChangeListener listener) {
38 | listeners.remove(listener);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 MonsterDruide1
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.
22 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/actions/ButtonAction.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.actions;
2 |
3 | import io.github.jadefalke2.Script;
4 | import io.github.jadefalke2.util.Button;
5 |
6 | public class ButtonAction implements Action {
7 |
8 | private final Script script;
9 | private final Button button;
10 | private final boolean enabled;
11 | private final int start, end;
12 | private boolean[] oldValues;
13 |
14 | public ButtonAction(Script script, Button button, boolean enabled, int start, int end) {
15 | this.script = script;
16 | this.button = button;
17 | this.enabled = enabled;
18 | this.start = start;
19 | this.end = end;
20 | }
21 |
22 | @Override
23 | public void execute() {
24 | oldValues = new boolean[end-start+1];
25 | for(int i=0; i newPosition).toArray(StickPosition[]::new));
29 | }
30 |
31 | @Override
32 | public void execute() {
33 | for (int i=0; i data) {
40 | int totalLength = 0;
41 | for (byte[] bytes : data) {
42 | totalLength += bytes.length;
43 | }
44 | byte[] result = new byte[totalLength];
45 | int currentPos = 0;
46 | for (byte[] bytes : data) {
47 | System.arraycopy(bytes, 0, result, currentPos, bytes.length);
48 | currentPos += bytes.length;
49 | }
50 | return result;
51 | }
52 | public static byte[] merge(byte[]... data) {
53 | int totalLength = 0;
54 | for (byte[] bytes : data) {
55 | totalLength += bytes.length;
56 | }
57 | byte[] result = new byte[totalLength];
58 | int currentPos = 0;
59 | for (byte[] bytes : data) {
60 | System.arraycopy(bytes, 0, result, currentPos, bytes.length);
61 | currentPos += bytes.length;
62 | }
63 | return result;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | io.github.jadefalke2
5 | TAS-Editor
6 | 1.0
7 |
8 |
9 |
10 | maven-jar-plugin
11 | 3.1.0
12 |
13 |
14 |
15 | true
16 | lib/
17 | io.github.jadefalke2.TAS
18 |
19 |
20 |
21 |
22 |
23 | maven-dependency-plugin
24 | 3.1.2
25 |
26 |
27 | copy-dependencies
28 | package
29 |
30 | copy-dependencies
31 |
32 |
33 | ${project.build.directory}/lib
34 | false
35 | false
36 | true
37 |
38 |
39 |
40 |
41 |
42 | maven-shade-plugin
43 | 3.2.4
44 |
45 |
46 | package
47 |
48 | shade
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 8
57 | 8
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/TAS.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2;
2 |
3 | import com.formdev.flatlaf.FlatDarkLaf;
4 | import com.formdev.flatlaf.FlatLightLaf;
5 | import io.github.jadefalke2.components.MainEditorWindow;
6 | import io.github.jadefalke2.util.Logger;
7 | import io.github.jadefalke2.util.Settings;
8 |
9 | import javax.swing.JFrame;
10 | import javax.swing.SwingUtilities;
11 | import javax.swing.UIManager;
12 | import javax.swing.UnsupportedLookAndFeelException;
13 | import java.awt.Window;
14 |
15 | public class TAS {
16 |
17 | private MainEditorWindow mainEditorWindow;
18 |
19 | public static void main(String[] args) {
20 | new TAS();
21 | }
22 |
23 | public TAS() {
24 | startProgram();
25 | }
26 |
27 | /**
28 | * starts the program by opening a new window with the two options of either creating a new script or loading in a preexisting one. After this it will start the editor.
29 | */
30 |
31 | public void startProgram() {
32 | Logger.log("boot up");
33 |
34 | // initialise preferences
35 | setLookAndFeel(Settings.INSTANCE.darkTheme.get());
36 | Settings.INSTANCE.darkTheme.attachListener(this::setLookAndFeel);
37 |
38 | mainEditorWindow = new MainEditorWindow(this);
39 | mainEditorWindow.openScript(Script.getEmptyScript(10));
40 | mainEditorWindow.setVisible(true);
41 |
42 | UIManager.put("FileChooser.useSystemExtensionHiding", false);
43 | UIManager.put("FileChooser.readOnly", true);
44 | }
45 |
46 | // set look and feels
47 |
48 | public void setLookAndFeel(boolean darkTheme){
49 | Logger.log("Changing theme: " + (darkTheme ? "Dark theme" : "Light theme"));
50 |
51 | try {
52 | UIManager.setLookAndFeel(darkTheme ? new FlatDarkLaf() : new FlatLightLaf());
53 | for(Window window : JFrame.getWindows()) {
54 | SwingUtilities.updateComponentTreeUI(window);
55 | }
56 | } catch (UnsupportedLookAndFeelException e) {
57 | e.printStackTrace();
58 | }
59 | }
60 |
61 | public void exit() {
62 | Logger.log("exiting program");
63 | mainEditorWindow.dispose();
64 | }
65 |
66 | public void newWindow() {
67 | Logger.log("opening new window");
68 | new TAS(); // TODO not the right way, as for example settings won't sync properly
69 | }
70 |
71 | public boolean closeAllScripts(){
72 | return mainEditorWindow.closeAllScripts();
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/components/ColumnRightClickMenu.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.components;
2 |
3 | import io.github.jadefalke2.InputLine;
4 | import io.github.jadefalke2.Script;
5 | import io.github.jadefalke2.actions.Action;
6 | import io.github.jadefalke2.actions.InsertEmptyLineAction;
7 | import io.github.jadefalke2.actions.LineAction;
8 | import io.github.jadefalke2.util.Button;
9 |
10 | import javax.swing.JCheckBoxMenuItem;
11 | import javax.swing.JMenuItem;
12 | import javax.swing.JPopupMenu;
13 | import javax.swing.table.TableColumnModel;
14 | import java.awt.Component;
15 | import java.awt.Point;
16 | import java.awt.datatransfer.UnsupportedFlavorException;
17 | import java.io.IOException;
18 |
19 | public class ColumnRightClickMenu extends JPopupMenu {
20 |
21 | private final JCheckBoxMenuItem[] items;
22 |
23 | public ColumnRightClickMenu(PianoRoll pianoRoll){
24 | TableColumnModel model = pianoRoll.getColumnModel();
25 | items = new JCheckBoxMenuItem[Button.values().length + 3];
26 |
27 | items[0] = new JCheckBoxMenuItem("Frame");
28 | items[1] = new JCheckBoxMenuItem("L-Stick");
29 | items[2] = new JCheckBoxMenuItem("R-Stick");
30 | for(int i = 3; i < items.length; i++){
31 | items[i] = new JCheckBoxMenuItem(Button.values()[i-3].toString());
32 | }
33 |
34 | for(int i=0; i {
37 | JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
38 | int index = -1;
39 | for(int j=0; j> 8), (byte)s};
12 | } else {
13 | return new byte[] {(byte)s, (byte)(s >> 8)};
14 | }
15 | }
16 |
17 | public static byte[] fromInt(int i, ByteOrder order) {
18 | if(order.equals(ByteOrder.BIG_ENDIAN)) {
19 | return new byte[] {
20 | (byte)(i >> 24),
21 | (byte)(i >> 16),
22 | (byte)(i >> 8),
23 | (byte)i};
24 | } else {
25 | return new byte[] {
26 | (byte)i,
27 | (byte)(i >> 8),
28 | (byte)(i >> 16),
29 | (byte)(i >> 24)};
30 | }
31 | }
32 |
33 | public static byte[] fromLong(long l, ByteOrder order) {
34 | if(order.equals(ByteOrder.BIG_ENDIAN)) {
35 | return new byte[] {
36 | (byte) (l >> 56),
37 | (byte) (l >> 48),
38 | (byte) (l >> 40),
39 | (byte) (l >> 32),
40 | (byte) (l >> 24),
41 | (byte) (l >> 16),
42 | (byte) (l >> 8),
43 | (byte) l};
44 | } else {
45 | return new byte[] {
46 | (byte) l,
47 | (byte) (l >> 8),
48 | (byte) (l >> 16),
49 | (byte) (l >> 24),
50 | (byte) (l >> 32),
51 | (byte) (l >> 40),
52 | (byte) (l >> 48),
53 | (byte) (l >> 56)};
54 | }
55 | }
56 |
57 | public static byte[] fromFloat(float f, ByteOrder order) {
58 | return fromInt(Float.floatToIntBits(f), order);
59 | }
60 |
61 | public static byte[] fromDouble(double d, ByteOrder order) {
62 | return fromLong(Double.doubleToLongBits(d), order);
63 | }
64 |
65 | public static byte[] fromBooleans(boolean... bools) {
66 | BitSet bits = new BitSet(bools.length);
67 | for (int i = 0; i < bools.length; i++) {
68 | if (bools[i]) {
69 | bits.set(i);
70 | }
71 | }
72 |
73 | byte[] bytes = bits.toByteArray();
74 | if (bytes.length * 8 >= bools.length) {
75 | return bytes;
76 | } else {
77 | return Arrays.copyOf(bytes, bools.length / 8 + (bools.length % 8 == 0 ? 0 : 1));
78 | }
79 | }
80 |
81 | public static int toInt(byte[] data, ByteOrder order) {
82 | if(order.equals(ByteOrder.BIG_ENDIAN)) {
83 | return
84 | ((data[0] & 0xff) << 24) |
85 | ((data[1] & 0xff) << 16) |
86 | ((data[2] & 0xff) << 8) |
87 | (data[3] & 0xff);
88 | } else {
89 | return
90 | ((int)data[0] & 0xff) |
91 | (((int)data[1] & 0xff) << 8) |
92 | (((int)data[2] & 0xff) << 16) |
93 | (((int)data[3] & 0xff) << 24);
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/components/AddLinesDialog.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.components;
2 |
3 | import io.github.jadefalke2.util.SimpleDocumentListener;
4 |
5 | import javax.swing.*;
6 | import java.awt.GridBagConstraints;
7 | import java.awt.GridBagLayout;
8 | import java.awt.Insets;
9 | import java.awt.Window;
10 | import java.awt.event.ActionListener;
11 | import java.awt.event.KeyEvent;
12 | import java.awt.event.WindowEvent;
13 | import java.util.ArrayList;
14 | import java.util.regex.Matcher;
15 | import java.util.regex.Pattern;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.IntStream;
18 |
19 | public class AddLinesDialog extends JDialog {
20 | private final int defaultValue;
21 |
22 | private final JTextField input;
23 | private boolean accepted = false;
24 | public AddLinesDialog(Window owner, int defaultValue) {
25 | super(owner, "Add multiple lines", ModalityType.APPLICATION_MODAL);
26 | this.defaultValue = defaultValue;
27 |
28 | getRootPane().registerKeyboardAction(e -> dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)), KeyStroke.getKeyStroke(
29 | KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
30 |
31 | JButton okButton = new JButton("OK");
32 | ActionListener accept = e -> {
33 | if(!isValidInput()) return;
34 | accepted = true;
35 | dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
36 | };
37 |
38 | setDefaultCloseOperation(DISPOSE_ON_CLOSE);
39 | JPanel mainPanel = new JPanel(new GridBagLayout());
40 | mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 15, 10, 15));
41 | GridBagConstraints c = new GridBagConstraints();
42 | c.fill = GridBagConstraints.HORIZONTAL;
43 | c.ipadx = 15;
44 | c.insets = new Insets(5, 0, 0, 0);
45 | c.weighty = 1;
46 | c.weightx = 1;
47 | c.gridx = 0;
48 | c.gridy = 0;
49 |
50 | input = new JTextField();
51 | input.getDocument().addDocumentListener((SimpleDocumentListener) e -> okButton.setEnabled(isValidInput()));
52 | input.addActionListener(accept);
53 | input.putClientProperty("JTextField.placeholderText", ""+defaultValue);
54 | mainPanel.add(input, c);
55 |
56 | c.gridy++;
57 | okButton.addActionListener(accept);
58 | mainPanel.add(okButton, c);
59 |
60 | add(mainPanel);
61 | pack();
62 | setLocationRelativeTo(owner);
63 | }
64 |
65 | public boolean isAccepted() {
66 | return accepted;
67 | }
68 |
69 | private Integer getValue() {
70 | String text = input.getText();
71 | if(text.isEmpty())
72 | return defaultValue;
73 |
74 | try {
75 | return Integer.parseInt(text);
76 | } catch(NumberFormatException e) {
77 | return null;
78 | }
79 | }
80 |
81 | private boolean isValidInput() {
82 | Integer val = getValue();
83 | return val != null && val >= 0;
84 | }
85 |
86 | public int getNumLines() {
87 | if(!isValidInput()) return -1;
88 |
89 | return getValue();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/components/TasFileChooser.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.components;
2 |
3 | import io.github.jadefalke2.script.Format;
4 | import io.github.jadefalke2.util.Settings;
5 | import io.github.jadefalke2.util.Util;
6 |
7 | import javax.swing.JFileChooser;
8 | import javax.swing.JOptionPane;
9 | import javax.swing.filechooser.FileNameExtensionFilter;
10 | import javax.swing.filechooser.FileSystemView;
11 | import java.awt.Dimension;
12 | import java.io.File;
13 |
14 | public class TasFileChooser extends JFileChooser {
15 |
16 | private static final FileNameExtensionFilter sTAS = new FileNameExtensionFilter("SwitchTAS files (.stas)", "stas");
17 | private static final FileNameExtensionFilter nxTAS = new FileNameExtensionFilter("nxTAS files (.txt)", "txt");
18 |
19 | private final File defaultDir;
20 | /**
21 | * Constructor
22 | */
23 | public TasFileChooser(File defaultDir){
24 | super(FileSystemView.getFileSystemView());
25 | this.defaultDir = defaultDir;
26 | setPreferredSize(new Dimension(1000,600));
27 | }
28 |
29 | /**
30 | * returns the chosen file
31 | * @param openFile whether it should display the opening or saving dialog
32 | * @return the chosen file
33 | */
34 | public File getFile (boolean openFile){
35 | setDialogTitle(openFile ? "Choose existing TAS file" : "Choose place to save");
36 | setCurrentDirectory(defaultDir);
37 | addChoosableFileFilter(sTAS);
38 | addChoosableFileFilter(nxTAS);
39 | setFileFilter(Settings.INSTANCE.defaultScriptFormat.get() == Format.STAS ? sTAS : nxTAS);
40 | int option = openFile ? showOpenDialog(null) : showSaveDialog(null);
41 |
42 | if (option == JFileChooser.APPROVE_OPTION) {
43 | return getSelectedFile();
44 | }
45 |
46 | return null;
47 | }
48 |
49 | public Format getFormat() {
50 | if(getFileFilter() == sTAS) return Format.STAS;
51 | else if(getFileFilter() == nxTAS) return Format.nxTAS;
52 | else throw new IllegalStateException("Unexpected value: " + getFileFilter());
53 | }
54 |
55 | @Override
56 | public void approveSelection(){
57 | File file = getSelectedFile();
58 |
59 | if (getFileFilter() == sTAS) {
60 | if(!file.getAbsolutePath().endsWith(".stas")) {
61 | file = new File(file.getAbsolutePath() + ".stas");
62 | setSelectedFile(file);
63 | }
64 | } else if(getFileFilter() == nxTAS) {
65 | if(!file.getAbsolutePath().endsWith(".txt")) {
66 | file = new File(file.getAbsolutePath() + ".txt");
67 | setSelectedFile(file);
68 | }
69 | }
70 |
71 | if(file.exists() && getDialogType() == SAVE_DIALOG){
72 | int result = JOptionPane.showConfirmDialog(this, "This file already exists, overwrite it?", "Existing file", JOptionPane.YES_NO_CANCEL_OPTION);
73 | switch (result) {
74 | case JOptionPane.YES_OPTION:
75 | super.approveSelection();
76 | return;
77 | case JOptionPane.NO_OPTION:
78 | case JOptionPane.CLOSED_OPTION:
79 | return;
80 | case JOptionPane.CANCEL_OPTION:
81 | cancelSelection();
82 | return;
83 | }
84 | }
85 | super.approveSelection();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | io.github.jadefalke2
8 | TAS-Editor
9 | 1.0
10 |
11 |
12 | 8
13 | 8
14 |
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-jar-plugin
22 | 3.1.0
23 |
24 |
25 |
26 | true
27 | lib/
28 | io.github.jadefalke2.TAS
29 |
30 |
31 |
32 |
33 |
34 | org.apache.maven.plugins
35 | maven-dependency-plugin
36 | 3.1.2
37 |
38 |
39 | copy-dependencies
40 | package
41 |
42 | copy-dependencies
43 |
44 |
45 | ${project.build.directory}/lib
46 | false
47 | false
48 | true
49 |
50 |
51 |
52 |
53 |
54 | org.apache.maven.plugins
55 | maven-shade-plugin
56 | 3.2.4
57 |
58 |
59 | package
60 |
61 | shade
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | com.formdev
72 | flatlaf
73 | 2.4
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/components/LineRightClickMenu.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.components;
2 |
3 | import io.github.jadefalke2.InputLine;
4 | import io.github.jadefalke2.Script;
5 | import io.github.jadefalke2.actions.Action;
6 | import io.github.jadefalke2.actions.InsertEmptyLineAction;
7 | import io.github.jadefalke2.actions.LineAction;
8 |
9 | import javax.swing.JMenuItem;
10 | import javax.swing.JPopupMenu;
11 | import java.awt.Component;
12 | import java.awt.Point;
13 | import java.awt.datatransfer.UnsupportedFlavorException;
14 | import java.io.IOException;
15 |
16 | public class LineRightClickMenu extends JPopupMenu {
17 |
18 | private final JMenuItem deleteOption;
19 | private final JMenuItem insertOption;
20 | private final JMenuItem cloneOption;
21 |
22 | private final JMenuItem cutOption;
23 | private final JMenuItem copyOption;
24 | private final JMenuItem pasteOption;
25 | private final JMenuItem replaceOption;
26 |
27 | private final Script script;
28 | private final ScriptTab scriptTab;
29 |
30 | public LineRightClickMenu(Script script, ScriptTab scriptTab){
31 | this.script = script;
32 | this.scriptTab = scriptTab;
33 |
34 | cutOption = add("cut");
35 | copyOption = add("copy");
36 | pasteOption = add("paste");
37 | replaceOption = add("replace");
38 |
39 | addSeparator();
40 |
41 | deleteOption = add("delete");
42 | insertOption = add("insert");
43 | cloneOption = add("clone");
44 |
45 | pack();
46 | }
47 |
48 | /**
49 | * opens the popup menu with the line actions
50 | * @param rows all rows affected by this method
51 | * @param point the point at which the menu "spawns"
52 | */
53 | public void openPopUpMenu(int[] rows, Point point, Component invoker){
54 |
55 | setListener(deleteOption, rows, LineAction.Type.DELETE);
56 | setListener(insertOption, new InsertEmptyLineAction(script, rows[rows.length-1]+1, rows.length));
57 | // insert dummy InputLines - unused, but the size is required for undo
58 | setListener(cloneOption, new LineAction(script, rows, new InputLine[rows.length], LineAction.Type.CLONE));
59 |
60 | setListener(cutOption, () -> scriptTab.getPianoRoll().cut());
61 | setListener(copyOption, () -> scriptTab.getPianoRoll().copy());
62 | setListener(pasteOption, () -> {
63 | try {
64 | scriptTab.getPianoRoll().paste();
65 | } catch (IOException | UnsupportedFlavorException e) {
66 | e.printStackTrace(); //TODO error handling
67 | }
68 | });
69 | setListener(replaceOption, () -> {
70 | try {
71 | scriptTab.getPianoRoll().replace();
72 | } catch (IOException | UnsupportedFlavorException e) {
73 | e.printStackTrace(); //TODO error handling
74 | }
75 | });
76 | show(invoker,(int)point.getX(),(int)point.getY());
77 | }
78 |
79 | public void setListener(JMenuItem item, Runnable action){
80 | while (item.getActionListeners().length > 0){
81 | item.removeActionListener(item.getActionListeners()[0]);
82 | }
83 |
84 | item.addActionListener(e -> action.run());
85 | }
86 |
87 | public void setListener(JMenuItem item, int[] rows, LineAction.Type type){
88 | setListener(item, new LineAction(script, rows, type));
89 | }
90 | public void setListener(JMenuItem item, Action action){
91 | setListener(item, () -> scriptTab.executeAction(action));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/util/Settings.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.util;
2 |
3 | import io.github.jadefalke2.TAS;
4 | import io.github.jadefalke2.script.Format;
5 | import io.github.jadefalke2.stickRelatedClasses.SmoothTransitionDialog;
6 |
7 | import java.io.File;
8 | import java.util.prefs.BackingStoreException;
9 | import java.util.prefs.Preferences;
10 |
11 | public class Settings {
12 |
13 | public static final Settings INSTANCE = new Settings(Preferences.userRoot().node(TAS.class.getName()));
14 | static {
15 | Runtime.getRuntime().addShutdownHook(new Thread(() -> {
16 | try {
17 | INSTANCE.storeSettings();
18 | } catch (BackingStoreException e) {
19 | e.printStackTrace();
20 | }
21 | }));
22 | }
23 |
24 | public final ObservableProperty directory;
25 | public final ObservableProperty darkTheme;
26 | public final ObservableProperty lastStickPositionCount;
27 | public final ObservableProperty joystickPanelPosition;
28 | public final ObservableProperty smoothTransitionType;
29 | public final ObservableProperty redoKeybind;
30 | public final ObservableProperty defaultScriptFormat;
31 | public final ObservableProperty authorName;
32 |
33 |
34 | private final Preferences backingPrefs;
35 |
36 | private Settings(Preferences prefs) {
37 | this.backingPrefs = prefs;
38 |
39 | File yuzuDir = new File(System.getProperty("user.home")+"/AppData/Roaming/yuzu/tas");
40 |
41 | directory = new ObservableProperty<>(new File(prefs.get("directory", System.getProperty("user.home")+(yuzuDir.exists() ? "/AppData/Roaming/yuzu/tas" : ""))));
42 | darkTheme = new ObservableProperty<>(prefs.get("darkTheme", "false").equals("true"));
43 | lastStickPositionCount = new ObservableProperty<>(Integer.parseInt(prefs.get("lastStickPositionCount", "3")));
44 | joystickPanelPosition = new ObservableProperty<>(JoystickPanelPosition.valueOf(prefs.get("joystickPanelPosition", "RIGHT")));
45 | smoothTransitionType = new ObservableProperty<>(SmoothTransitionDialog.SmoothTransitionType.valueOf(prefs.get("smoothTransitionType", "ANGULAR_CLOSEST")));
46 | redoKeybind = new ObservableProperty<>(RedoKeybind.valueOf(prefs.get("redoKeybind", "CTRL_SHIFT_Z")));
47 | defaultScriptFormat = new ObservableProperty<>(Format.valueOf(prefs.get("defaultScriptFormat", "nxTAS")));
48 | authorName = new ObservableProperty<>(prefs.get("authorName", ""));
49 | }
50 |
51 | public void storeSettings() throws BackingStoreException {
52 | backingPrefs.clear();
53 |
54 | backingPrefs.put("directory", directory.get() + "");
55 | backingPrefs.put("darkTheme", darkTheme.get() + "");
56 | backingPrefs.put("lastStickPositionCount", lastStickPositionCount.get() + "");
57 | backingPrefs.put("joystickPanelPosition", joystickPanelPosition.get() + "");
58 | backingPrefs.put("smoothTransitionType", smoothTransitionType.get() + "");
59 | backingPrefs.put("redoKeybind", redoKeybind.get() + "");
60 | backingPrefs.put("defaultScriptFormat", defaultScriptFormat.get() + "");
61 | backingPrefs.put("authorName", authorName.get());
62 |
63 | backingPrefs.flush();
64 | }
65 |
66 | public enum JoystickPanelPosition {
67 | LEFT, RIGHT, HIDDEN
68 | }
69 |
70 | public enum RedoKeybind {
71 | CTRL_SHIFT_Z, CTRL_Y
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/io/github/jadefalke2/script/NXTas.java:
--------------------------------------------------------------------------------
1 | package io.github.jadefalke2.script;
2 |
3 | import io.github.jadefalke2.InputLine;
4 | import io.github.jadefalke2.Script;
5 | import io.github.jadefalke2.stickRelatedClasses.StickPosition;
6 | import io.github.jadefalke2.util.Button;
7 | import io.github.jadefalke2.util.CorruptedScriptException;
8 | import io.github.jadefalke2.util.Logger;
9 | import io.github.jadefalke2.util.Util;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.util.ArrayList;
14 | import java.util.EnumSet;
15 | import java.util.List;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.IntStream;
18 |
19 | public class NXTas {
20 |
21 | public static void write(Script script, File file) throws IOException {
22 | Logger.log("saving script to " + file.getAbsolutePath());
23 | Util.writeFile(write(script), file);
24 | }
25 |
26 | public static String write(Script script) {
27 | InputLine[] inputLines = script.getLines();
28 | return IntStream.range(0, inputLines.length).filter(i -> !inputLines[i].isEmpty()).mapToObj(i -> inputLines[i].getFull(i) + "\n").collect(
29 | Collectors.joining());
30 | }
31 |
32 | public static Script read(File file) throws CorruptedScriptException, IOException {
33 | Script s = read(Util.fileToString(file));
34 | s.setFile(file, Format.nxTAS);
35 | return s;
36 | }
37 |
38 | public static Script read(String script) throws CorruptedScriptException {
39 | List inputLines = new ArrayList<>();
40 | String[] lines = script.split("\n");
41 |
42 | int currentFrame = 0;
43 |
44 | for (String line : lines) {
45 | InputLine currentInputLine = readLine(line);
46 | int frame = Integer.parseInt(line.split(" ")[0]);
47 |
48 | if (frame < currentFrame){
49 | throw new CorruptedScriptException("Line numbers misordered", currentFrame);
50 | }
51 |
52 | while(currentFrame < frame){
53 | inputLines.add(InputLine.getEmpty());
54 | currentFrame++;
55 | }
56 |
57 | inputLines.add(currentInputLine);
58 | currentFrame++;
59 | }
60 | return new Script(inputLines.toArray(new InputLine[0]), 0);
61 | }
62 |
63 | public static InputLine readLine(String full) throws CorruptedScriptException {
64 | if (full.isEmpty()){
65 | throw new CorruptedScriptException("Empty InputLine", -1);
66 | }
67 |
68 | int frame = 0;
69 | try {
70 | String[] components = full.split(" ");
71 |
72 | EnumSet