├── .gitattributes ├── icon.png ├── src ├── font │ ├── cherry.bmp │ ├── fontCherry.fnt │ ├── fontCherry.c │ └── fontCherry.h ├── asm │ ├── spi.h │ ├── lexer.h │ ├── include │ │ └── equates.inc │ ├── rodata.asm │ ├── misc.h │ ├── spi.asm │ ├── files.h │ ├── misc.asm │ ├── sortVat.asm │ ├── files.asm │ └── lexer.asm ├── assembler.h ├── edit.h ├── parser.h ├── highlight.h ├── gfx │ └── convimg.yaml ├── menu.h ├── ui.h ├── highlight.c ├── utility.h ├── main.c ├── defines.h ├── parser.c ├── ui.c ├── utility.c ├── edit.c ├── assembler.c └── menu.c ├── screenshots ├── about.png ├── settings.gif ├── ez80-studio.gif └── other-menus.gif ├── .gitignore ├── includes ├── changes.txt ├── include │ └── tiformat.inc ├── conv-inc.py └── TIOSFLAG.inc ├── makefile ├── convert.sh ├── readme.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.inc linguist-vendored 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/icon.png -------------------------------------------------------------------------------- /src/font/cherry.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/src/font/cherry.bmp -------------------------------------------------------------------------------- /screenshots/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/screenshots/about.png -------------------------------------------------------------------------------- /src/font/fontCherry.fnt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/src/font/fontCherry.fnt -------------------------------------------------------------------------------- /screenshots/settings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/screenshots/settings.gif -------------------------------------------------------------------------------- /screenshots/ez80-studio.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/screenshots/ez80-studio.gif -------------------------------------------------------------------------------- /screenshots/other-menus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EzCE/ez80-studio/HEAD/screenshots/other-menus.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | concept/ 2 | obj/ 3 | bin/ 4 | includes/obj/ 5 | includes/bin/ 6 | .vscode/ 7 | src/gfx/*.c 8 | src/gfx/*.h 9 | src/font/*.inc 10 | convimg.out 11 | nul.d 12 | src/gfx/convimg.yaml.lst 13 | *.zip 14 | -------------------------------------------------------------------------------- /src/font/fontCherry.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - fontCherry.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * 7 | * Cherry font by turquoise-hexagon 8 | * Slighly modified from the original 9 | * 10 | * Copyright 2022 - 2025 11 | * License: GPL-3.0 12 | * 13 | * -------------------------------------- 14 | **/ 15 | 16 | #include "fontCherry.h" 17 | 18 | static const uint8_t fontCherryData[] = { 19 | #include "fontCherry.inc" 20 | }; 21 | 22 | const fontlib_font_t *fontCherry = (fontlib_font_t *)fontCherryData; 23 | -------------------------------------------------------------------------------- /includes/changes.txt: -------------------------------------------------------------------------------- 1 | Include conversion script for BASM-3 by beckadamtheinventor: https://github.com/beckadamtheinventor/BASM-3/ 2 | 3 | The conversion script has been modified to support lowercase letters and the '_' and '.' characters, along with a modified header. 4 | It has also been updated to fix a bug with data size detection. 5 | The original can be found here: https://github.com/beckadamtheinventor/BASM-3/blob/master/data/conv-inc.py 6 | 7 | TI84PCEG_smaller.inc, TIOSFLAG.inc, TIOSKBEQ.inc, TIOSRAMA.inc, and TIOSRTNS.inc have all been modified to include updated names 8 | and to fix some symbol definitions. 9 | -------------------------------------------------------------------------------- /src/font/fontCherry.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - fontCherry.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * 7 | * Cherry font by turquoise-hexagon 8 | * Slighly modified from the original 9 | * 10 | * Copyright 2022 - 2025 11 | * License: GPL-3.0 12 | * 13 | * -------------------------------------- 14 | **/ 15 | 16 | #ifndef FONTCHERRY_H 17 | #define FONTCHERRY_H 18 | 19 | #include 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | extern const fontlib_font_t *fontCherry; 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/asm/spi.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - spi.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef SPI_H 13 | #define SPI_H 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | /** 20 | * @brief Marks the beginning of a logical frame. 21 | * 22 | */ 23 | void asm_spi_BeginFrame(void); 24 | 25 | /** 26 | * @brief Marks the end of a logical frame. 27 | * 28 | */ 29 | void asm_spi_EndFrame(void); 30 | 31 | /** 32 | * @brief Necessary for Python models. 33 | * 34 | */ 35 | void asm_spi_SetupSPI(void); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/asm/lexer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - lexer.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef LEXER_H 13 | #define LEXER_H 14 | 15 | #include "defines.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Gets the type of a specified token. 23 | * 24 | * @param tokStart Pointer to start of token. 25 | * @param tokEnd Pointer to end of token (start of next token). 26 | * @return uint8_t Token type, or 0 if not found. 27 | */ 28 | uint8_t asm_lexer_TokType(char *tokStart, char *tokEnd); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif -------------------------------------------------------------------------------- /src/assembler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - assembler.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef ASSEMBLER_H 13 | #define ASSEMBLER_H 14 | 15 | #include "defines.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Check if a string is a 1-byte char. 23 | * 24 | * @param string String to evaluate 25 | * @return true String is a char. 26 | * @return false String is not a char. 27 | */ 28 | bool assembler_IsChar(char *string); 29 | 30 | /** 31 | * @brief Assemble the currently open file. 32 | * 33 | * @param studioContext eZ80 Studio context struct. 34 | * @return struct error_t Error returned by assembler. 35 | */ 36 | struct error_t assembler_Main(struct context_t *studioContext); 37 | 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | #-------------------------------------- 2 | # 3 | # eZ80 Studio Source Code - makefile 4 | # By RoccoLox Programs and TIny_Hacker 5 | # Copyright 2022 - 2025 6 | # License: GPL-3.0 7 | # 8 | #-------------------------------------- 9 | 10 | NAME = EZSTUDIO 11 | ICON = icon.png 12 | DESCRIPTION = "eZ80 Assembly IDE" 13 | COMPRESSED = YES 14 | ARCHIVED = YES 15 | VERSION = 1.0.0 16 | 17 | CFLAGS = -Wall -Wextra -Oz -DVERSION_NO=\"$(VERSION)\" 18 | CXXFLAGS = -Wall -Wextra -Oz -DVERSION_NO=\"$(VERSION)\" 19 | 20 | FONTDIR = $(SRCDIR)/font 21 | FONT = $(FONTDIR)/fontCherry.fnt 22 | FONT_INC = $(FONTDIR)/fontCherry.inc 23 | 24 | DEPS = $(FONT_INC) 25 | 26 | # ---------------------------- 27 | 28 | include $(shell cedev-config --makefile) 29 | 30 | $(FONT_INC): $(FONT) 31 | $(Q)$(call MKDIR,$(@D)) 32 | $(Q)convfont -o carray -f $< -a 1 -b 1 -w bold -c 2 -x 9 -l 0x0B -Z $@ 33 | 34 | final: 35 | make 36 | convbin -r -e zx0 -k 8xp-compressed -u -n $(NAME) -i bin/$(NAME).bin -o bin/$(NAME).8xp 37 | -------------------------------------------------------------------------------- /src/edit.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - edit.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef EDIT_H 13 | #define EDIT_H 14 | 15 | #include "defines.h" 16 | 17 | #include 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | /** 24 | * @brief Redraw the editor view. 25 | * 26 | * @param studioContext eZ80 Studio context struct. 27 | * @param studioPreferences eZ80 Studio preferences struct. 28 | */ 29 | void edit_RedrawEditor(struct context_t *studioContext, struct preferences_t *studioPreferences); 30 | 31 | /** 32 | * @brief Opens the editor view. 33 | * 34 | * @param studioContext eZ80 Studio context struct. 35 | * @param studioPreferences eZ80 Studio preferences struct. 36 | */ 37 | void edit_OpenEditor(struct context_t *studioContext, struct preferences_t *studioPreferences); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif -------------------------------------------------------------------------------- /src/asm/include/equates.inc: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - equates.inc 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Copyright 2022 - 2025 6 | ; License: GPL-3.0 7 | ; 8 | ;-------------------------------------- 9 | 10 | include 'ti84pceg.inc' 11 | 12 | ; Flags 13 | 14 | sortFlag := ti.asm_Flag1 15 | sortFirstItemFound := 0 16 | sortFirstHidden := 1 17 | sortSecondHidden := 2 18 | 19 | ; Editor 20 | 21 | editBuffer := ti.vRam + ti.lcdWidth * ti.lcdHeight + 2 22 | 23 | ; Assembler 24 | 25 | symbolTableStart := editBuffer + 65500 + 128 26 | 27 | ; SPI defines 28 | 29 | spiValid := 8 30 | 31 | ; Lexer types 32 | typeDefault := 3 33 | typeLabel := 4 34 | typeInstruction := 5 35 | typeRegister := 6 36 | typeCondition := 6 37 | typeNumber := 7 38 | typeParenthesis := 8 39 | typeString := 9 40 | typeComment := 10 41 | typeModifier := 11 42 | 43 | ; use cursorImage RAM for these variables 44 | ;----------------------------------------- 45 | 46 | EOF := ti.cursorImage 47 | 48 | currentLineStart := EOF + 3 49 | oldLineStart := currentLineStart + 3 50 | 51 | ; VAT / Filesystem 52 | 53 | sortFirstItemFoundPtr := ti.mpLcdCrsrImage 54 | sortEndOfPartPtr := ti.mpLcdCrsrImage + 3 55 | sortVatEntrySize := ti.mpLcdCrsrImage + 6 56 | sortVatEntryNewLoc := ti.mpLcdCrsrImage + 9 57 | sortVatEntryTempEnd := ti.mpLcdCrsrImage + 12 + 15 58 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - parser.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | /** 13 | * @brief Parse bitwise OR. 14 | * 15 | * @return long Parsing result. 16 | */ 17 | long parser_BR(void); 18 | 19 | /** 20 | * @brief Parse bitwise XOR. 21 | * 22 | * @return long Parsing result. 23 | */ 24 | long parser_BX(void); 25 | 26 | /** 27 | * @brief Parse bitwise AND. 28 | * 29 | * @return long Parsing result. 30 | */ 31 | long parser_BA(void); 32 | 33 | /** 34 | * @brief Parse left / right bitshift. 35 | * 36 | * @return long Parsing result. 37 | */ 38 | long parser_S(void); 39 | 40 | /** 41 | * @brief Parse expression. 42 | * 43 | * @return long Parsing result. 44 | */ 45 | long parser_E(void); 46 | 47 | /** 48 | * @brief Parse term. 49 | * 50 | * @return long Parsing result. 51 | */ 52 | long parser_T(void); 53 | 54 | /** 55 | * @brief Parse factor. 56 | * 57 | * @return long Parsing result. 58 | */ 59 | long parser_F(void); 60 | 61 | /** 62 | * @brief Evalute a string containing a mathematical expression. 63 | * 64 | * @param input String to evaluate. 65 | * @param error Address to store error code at. 66 | * @return unsigned long Result of evaluation. 67 | */ 68 | unsigned long parser_Eval(char *input, uint8_t *error); 69 | -------------------------------------------------------------------------------- /src/highlight.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - highlight.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef HIGHLIGHT_H 13 | #define HIGHLIGHT_H 14 | 15 | #include 16 | #include 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | /** 23 | * @brief Gets a usable token from a string. 24 | * 25 | * @param string Start of token string. 26 | * @param stringEnd End of token string. 27 | * @return char* Converted token. 28 | */ 29 | char *hlight_GetTokenString(char *string, char *stringEnd); 30 | 31 | /** 32 | * @brief Check if a token is a number (including bases). 33 | * 34 | * @param string Start of token string. 35 | * @param stringEnd End of token string. 36 | * @return true Token is a number. 37 | * @return false Token is not a number. 38 | */ 39 | bool hlight_Number(char *string, char *stringEnd); 40 | 41 | /** 42 | * @brief Finds the color to highlight a specific token. 43 | * 44 | * @param string Pointer to start of token. 45 | * @param stringEnd Pointer to end of token. 46 | * @param highlighting Whether highlighting is enabled. 47 | * @return uint8_t Highlight color. 48 | */ 49 | uint8_t hlight_GetHighlightColor(char *string, char *stringEnd, bool highlighting); 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | 55 | #endif -------------------------------------------------------------------------------- /src/asm/rodata.asm: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - rodata.asm 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Copyright 2022 - 2025 6 | ; License: GPL-3.0 7 | ; 8 | ;-------------------------------------- 9 | 10 | assume adl=1 11 | 12 | section .rodata 13 | 14 | include 'include/equates.inc' 15 | 16 | public _rodata_characters 17 | public _rodata_sizeOfCharsLUT 18 | 19 | _rodata_characters: 20 | ; numbers 21 | db 10 ; ENTER 22 | db '+-*/^', 0, 0 ; + - × ÷ ^ undef undef 23 | db '-369)', 0, 0, 0 ; (-) 3 6 9 ) TAN VARS undef 24 | db '.258(', 0, 0, 0 ; . 2 5 8 ( COS PRGM STAT 25 | db '0147,', 0, 0, 'X', 0 ; 0 1 4 7 , SIN APPS XT?n undef 26 | db 0, 0, 0, 0, 0, 0 ; STO LN LOG x2 x-1 MATH 27 | _rodata_sizeOfCharsLUT := $ - _rodata_characters 28 | 29 | ; uppercase letters 30 | db 10 ; ENTER 31 | db 34, 'WRMH', 0, 0 ; + - × ÷ ^ undef undef 32 | db '?', 0, 'VQLG', 0, 0 ; (-) 3 6 9 ) TAN VARS undef 33 | db ':ZUPKFC', 0 ; . 2 5 8 ( COS PRGM STAT 34 | db ' YTOJEBX', 0 ; 0 1 4 7 , SIN APPS XT?n undef 35 | db 'XSNIDA' ; STO LN LOG x2 x-1 MATH 36 | 37 | ; lowercase letters 38 | db 10 ; ENTER 39 | db 34, 'wrmh', 0, 0 ; + - × ÷ ^ undef undef 40 | db '?', 0, 'vqlg', 0, 0 ; (-) 3 6 9 ) TAN VARS undef 41 | db ':zupkfc', 0 ; . 2 5 8 ( COS PRGM STAT 42 | db ' ytojebX', 0 ; 0 1 4 7 , SIN APPS XT?n undef 43 | db 'xsnida' 44 | -------------------------------------------------------------------------------- /src/gfx/convimg.yaml: -------------------------------------------------------------------------------- 1 | palettes: 2 | - name: lightPalette 3 | fixed-entries: 4 | - color: {index: 0, r: 253, g: 246, b: 227} # Background 5 | - color: {index: 1, r: 165, g: 142, b: 81} # Outline 6 | - color: {index: 2, r: 217, g: 193, b: 127} # Cursor 7 | - color: {index: 3, r: 38, g: 50, b: 56} # Default text 8 | - color: {index: 4, r: 57, g: 111, b: 226} # Labels 9 | - color: {index: 5, r: 98, g: 137, b: 40} # Instructions 10 | - color: {index: 6, r: 233, g: 120, b: 17} # Registers 11 | - color: {index: 7, r: 211, g: 54, b: 130} # Numbers 12 | - color: {index: 8, r: 108, g: 113, b: 196} # Parenthesis 13 | - color: {index: 9, r: 255, g: 124, b: 89} # Strings 14 | - color: {index: 10, r: 101, g: 123, b: 131} # Comments 15 | - name: darkPalette 16 | fixed-entries: 17 | - color: {index: 0, r: 38, g: 50, b: 56} # Background 18 | - color: {index: 1, r: 28, g: 32, b: 34} # Outline 19 | - color: {index: 2, r: 69, g: 90, b: 100} # Cursor 20 | - color: {index: 3, r: 120, g: 144, b: 156} # Default text 21 | - color: {index: 4, r: 250, g: 237, b: 112} # Labels 22 | - color: {index: 5, r: 199, g: 146, b: 234} # Instructions 23 | - color: {index: 6, r: 57, g: 111, b: 226} # Registers 24 | - color: {index: 7, r: 158, g: 206, b: 88} # Numbers 25 | - color: {index: 8, r: 255, g: 203, b: 107} # Parenthesis 26 | - color: {index: 9, r: 255, g: 124, b: 89} # Strings 27 | - color: {index: 10, r: 98, g: 137, b: 40} # Comments 28 | 29 | 30 | outputs: 31 | - type: c 32 | include-file: gfx.h 33 | palettes: 34 | - darkPalette 35 | - lightPalette 36 | const: true 37 | -------------------------------------------------------------------------------- /src/asm/misc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - misc.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef MISC_H 13 | #define MISC_H 14 | 15 | #include "defines.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Checks if a string matches an opcode. 23 | * 24 | * @param string String to compare. 25 | * @return struct opcode_t* Pointer to location in opcode table if matched, or NULL if not. 26 | */ 27 | struct opcode_t *asm_misc_FindOpcode(char *string); 28 | 29 | /** 30 | * @brief Converts a string such as "123" to an integer value. 31 | * 32 | * @param string String to convert. 33 | * @return int Result of conversion, or -1 if the string was invalid. 34 | */ 35 | int asm_misc_StringToInt(char *string); 36 | 37 | /** 38 | * @brief Clears an 8bpp VRAM buffer. 39 | * 40 | * @param buffer Pointer to start of buffer. 41 | */ 42 | void asm_misc_ClearBuffer(void *buffer); 43 | 44 | /** 45 | * @brief Sorts the VAT alphabetically. 46 | * 47 | */ 48 | void asm_misc_SortVAT(void); 49 | 50 | /** 51 | * @brief Scans the keypad and returns a character based on the key pressed. 52 | * 53 | * @param inputMode Input mode, like 2nd mode, uppercase mode, or lowercase mode. 54 | * @return char Character found from keypress. 55 | */ 56 | char asm_misc_GetCharFromKey(uint8_t inputMode); 57 | 58 | /** 59 | * @brief Copy backwards (lddr) from src -> dest, for a specified number of bytes. 60 | * 61 | * @param src Pointer to first byte of data source. 62 | * @param dest Pointer to first byte of data destination. 63 | * @param bytes Bytes to copy. 64 | */ 65 | void asm_misc_ReverseCopy(void *src, void *dest, unsigned int bytes); 66 | 67 | /** 68 | * @brief Retrieve a value for a specified symbol from the symbol table. 69 | * 70 | * @param search Symbol to search for. 71 | * @param table Pointer to start of symbol table. 72 | */ 73 | void *asm_misc_FindSymbol(char *search, char *table); 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - menu.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef MENU_H 13 | #define MENU_H 14 | 15 | #include "defines.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Displays an error message based on the specified error code. 23 | * 24 | * @param error Error message info to display. 25 | */ 26 | void menu_Error(struct error_t error); 27 | 28 | /** 29 | * @brief Displays an option box for yes or no. 30 | * 31 | * @param x X coordinate to begin drawing the box. 32 | * @param y Y coordinate to begin drawing the box. 33 | * @param buttonWidth Width of the yes and no buttons. 34 | * @return true The user selected yes. 35 | * @return false The user selected no. 36 | */ 37 | bool menu_YesNo(unsigned int x, uint8_t y, uint8_t buttonWidth); 38 | 39 | /** 40 | * @brief Displays a warning message based on the specified warning code. 41 | * 42 | * @param warning Warning message code. 43 | * @return true Warning was acknowledged. 44 | * @return false Warning was ignored. 45 | */ 46 | bool menu_Warning(uint8_t warning); 47 | 48 | /** 49 | * @brief Opens the file options menu. 50 | * 51 | * @param studioContext eZ80 Studio context struct. 52 | * @param studioPreferences eZ80 Studio preferences struct. 53 | */ 54 | void menu_File(struct context_t *studioContext, struct preferences_t *studioPreferences); 55 | 56 | /** 57 | * @brief Opens the line goto menu. 58 | * 59 | * @param studioContext eZ80 Studio context struct. 60 | * @return The character selected, or '\0' if none were selected. 61 | */ 62 | void menu_Goto(struct context_t *studioContext); 63 | 64 | /** 65 | * @brief Opens the special characters menu. 66 | * 67 | * @param studioContext eZ80 Studio context struct. 68 | */ 69 | char menu_Chars(struct context_t *studioContext); 70 | 71 | /** 72 | * @brief Opens the settings menu. 73 | * 74 | * @param studioContext eZ80 Studio context struct. 75 | * @param studioPreferences eZ80 Studio preferences struct. 76 | */ 77 | void menu_Settings(struct context_t *studioContext, struct preferences_t *studioPreferences); 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | 83 | #endif -------------------------------------------------------------------------------- /convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | INPUT_FILE=$1 4 | OUTPUT_FILE=$2 5 | MODE=$3 6 | 7 | if [[ $# -ne 3 ]] # Ensure proper number of arguments 8 | then 9 | echo "Incorrect number of arguments supplied. Proper syntax is ./convert.sh " 10 | echo ": Input file to specified operation." 11 | echo ": Output file to specified operation." 12 | echo ": Use 'var' to convert an eZ80 Studio AppVar to assembly source, or 'asm' to convert assembly source to an eZ80 Studio AppVar." 13 | exit 1 14 | fi 15 | 16 | if [[ ! -f $INPUT_FILE ]] # Ensure the specified file exists 17 | then 18 | echo "The specified file does not exist. Please ensure that the file and path specified were correct and try again." 19 | exit 1 20 | fi 21 | 22 | if [[ $MODE == "var" ]] # Convert AppVar to assembly source file 23 | then 24 | echo "Converting from AppVar..." 25 | 26 | convbin -j 8x -k bin -i $INPUT_FILE -o $OUTPUT_FILE 27 | 28 | echo "AppVar converted successfully." 29 | echo "Removing header..." 30 | 31 | sed -i 's/\xEF\x7A//g' $OUTPUT_FILE # Remove header 32 | 33 | echo "Header removed." 34 | 35 | elif [[ $MODE == "asm" ]] # Convert assembly source file to AppVar 36 | then 37 | echo "Adding header..." 38 | 39 | echo -n -e '\xEF\x7A' | cat - $INPUT_FILE > temp_$INPUT_FILE 40 | 41 | if [[ ! -f temp_$INPUT_FILE ]] # Ensure the specified file exists 42 | then 43 | echo "The header was not able to be added. Ensure you are in the directory with your input file and try again." 44 | exit 1 45 | fi 46 | 47 | echo "Header added succesfully." 48 | echo "Converting newlines..." 49 | 50 | sed -i 's/\r\n/\n/g' temp_$INPUT_FILE # Get rid of windows garbage (Hopefully nothing goes wrong here) 51 | 52 | echo "Newlines converted succesfully." 53 | echo "Converting tabs..." 54 | 55 | sed -i 's/\t/ /g' temp_$INPUT_FILE 56 | 57 | echo "Tabs converted succesfully." 58 | echo "Converting to AppVar format..." 59 | 60 | convbin -j bin -k 8xv -i temp_$INPUT_FILE -o $OUTPUT_FILE.8xv -n $OUTPUT_FILE 61 | 62 | if [[ ! -f $OUTPUT_FILE.8xv ]] # Ensure the file got created 63 | then 64 | echo "The conversion failed. Please ensure convbin is installed on your system and try again." 65 | exit 1 66 | fi 67 | 68 | rm temp_$INPUT_FILE 69 | 70 | else 71 | echo "Invalid mode. Use 'var' to convert an eZ80 Studio AppVar to assembly source, or 'asm' to convert assembly source to an eZ80 Studio AppVar." 72 | exit 1 73 | fi 74 | 75 | echo "Success!" 76 | -------------------------------------------------------------------------------- /includes/include/tiformat.inc: -------------------------------------------------------------------------------- 1 | macro format?.ti? clause& 2 | local all, checkloc, checksum, cplx, data, exec, flag, head, name, process, prot, temp, type, ext, var 3 | macro process: setting 4 | match first rest, setting 5 | process first 6 | process rest 7 | else match =ARCHIVED?, setting 8 | flag = flag or 1 shl 7 9 | else match =EXECUTABLE?, setting 10 | define exec 11 | else match =PROTECTED?, setting 12 | type = 6 13 | define prot 14 | else match =REAL?, setting 15 | type = 0 16 | ext = '8xn' 17 | else match =LIST?, setting 18 | if defined cplx 19 | type = 13 20 | else 21 | type = 2 22 | end if 23 | ext = '8xl' 24 | else match =MATRIX?, setting 25 | type = 2 26 | ext = '8xm' 27 | else match =EQUATION?, setting 28 | type = 3 29 | ext = '8xy' 30 | else match =STRING?, setting 31 | type = 4 32 | ext = '8xs' 33 | else match =PROGRAM?, setting 34 | if defined prot 35 | type = 6 36 | else 37 | type = 5 38 | end if 39 | ext = '8xp' 40 | else match =PICTURE?, setting 41 | type = 7 42 | ext = '8ci' 43 | else match =GDB?, setting 44 | type = 1 45 | ext = '8xd' 46 | else match =COMPLEX?, setting 47 | type = 12 48 | define cplx 49 | ext = '8xc' 50 | else match =APPVAR?, setting 51 | type = 21 52 | ext = '8xv' 53 | else match =GROUP?, setting 54 | type = 23 55 | ext = '8xg' 56 | else match =IMAGE?, setting 57 | type = 26 58 | ext = '8ca' 59 | else if setting eqtype 'name' 60 | name = setting 61 | else if setting eqtype 0 62 | type = setting 63 | ext = '8x' 64 | end if 65 | end macro 66 | name = 'A' 67 | type = 6 68 | ext = '8xp' 69 | flag = 0 70 | process clause 71 | format binary as ext 72 | assert ~$% & 'Unexpected data before format directive' 73 | dq '**TI83F*' 74 | db 26, 10, 0 75 | rb 42 76 | dw sizeof all 77 | label all: checkloc - $% 78 | dw sizeof head, sizeof data 79 | label head: data - $ 80 | db type 81 | dq name 82 | db 0, flag 83 | dw sizeof data 84 | label data: checkloc - $% 85 | dw sizeof var 86 | label var: checkloc - $% 87 | if defined exec 88 | dw 07BEFh 89 | org 0D1A881h 90 | else 91 | org 0 92 | end if 93 | postpone 94 | label checkloc at $% 95 | end postpone 96 | postpone ? 97 | assert checkloc = $% & 'Unexpected postpone before format directive' 98 | checksum = 0 99 | repeat sizeof all 100 | load temp: byte from: all + %-1 101 | checksum = (checksum + temp) and 0FFFFh 102 | end repeat 103 | dw checksum 104 | end postpone 105 | end macro 106 | -------------------------------------------------------------------------------- /includes/conv-inc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os,sys 3 | 4 | try: 5 | os.makedirs("bin") 6 | except: 7 | pass 8 | 9 | try: 10 | os.makedirs("obj") 11 | except: 12 | pass 13 | 14 | try: 15 | fname = sys.argv[1] 16 | except: 17 | fname = input("include file to convert?") 18 | 19 | try: 20 | with open(fname+".inc","r") as f: 21 | lines = f.read().splitlines() 22 | except: 23 | quit() 24 | 25 | letters = {l:[] for l in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_."} 26 | 27 | def detectBase(line,i): 28 | j = i 29 | while line[j] in "0123456789ABCDEF": 30 | j+=1 31 | if j>=len(line): break 32 | base=10 33 | if j=len(line): break 53 | if ii: 56 | word = line[1:k].replace("\t","").replace(" ","") 57 | try: 58 | dt = [word,int(line[i:j],base)] 59 | letters[word[0]].append(dt) 60 | except Exception as e: 61 | print("[WARNING] Internal Error:",e) 62 | elif ":;basm-macro" in line: 63 | pass 64 | # k = line.find(":;basm_macro") 65 | # word = line[1:k].upper().replace("_","[").replace(".","[").replace("\t","").replace(" ","") 66 | # toasm = [] 67 | # while line!=";end macro": 68 | # line = lines[lx]; lx+=1 69 | # toasm.append(line) 70 | 71 | with open("obj/"+fname+".bin","wb") as f: 72 | s=b"eZ80 INC" 73 | f.write(s) 74 | tbloff = 8 75 | f.write(bytes([0]*108)) 76 | tbl = [] 77 | for letter in letters.keys(): 78 | tbl.append(f.tell()) 79 | for word in letters[letter]: 80 | f.write(bytes(word[0],'UTF-8')) 81 | arg=[word[1]&0xFF,(word[1]//0x100)&0xFF,(word[1]//0x10000)&0xFF] 82 | a=3 83 | for i in range(2, -1, -1): 84 | if arg[i]: 85 | break 86 | else: 87 | a-=1 88 | if not a: a=1 89 | f.write(bytes([0,a]+arg[:a])) 90 | f.write(bytes([0])) 91 | 92 | size=f.tell() 93 | if size>0xFFE8: 94 | print("File too large!",size) 95 | quit() 96 | 97 | f.seek(tbloff) 98 | for i in range(54): 99 | f.write(bytes([tbl[i]&0xFF,(tbl[i]//0x100)&0xFF])) 100 | 101 | with open("obj/"+fname+".asm","w") as f: 102 | if len(fname)>8: name = fname[:8] 103 | else: name=fname 104 | f.write("include 'include/tiformat.inc'\nformat ti archived appvar '"+name+"'\nfile 'obj/"+fname+".bin'") 105 | 106 | os.system("fasmg obj/"+fname+".asm bin/"+fname+".8xv") 107 | -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - ui.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef UI_H 13 | #define UI_H 14 | 15 | #include "defines.h" 16 | 17 | #include 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | /** 24 | * @brief Draws a scrollbar 25 | * 26 | * @param x X coordinate to begin drawing the scrollbar at. 27 | * @param y Y coordinate to begin drawing the scrollbar at. 28 | * @param boxHeight Total height of the scrollbar box. 29 | * @param totalLines Number of total lines that the scrollbar represents. 30 | * @param startLine Line at which the scrollbar starts. 31 | * @param linesPerPage Number of lines per page. 32 | */ 33 | void ui_DrawScrollbar(unsigned int x, uint8_t y, uint8_t boxHeight, unsigned int totalLines, unsigned int startLine, uint8_t linesPerPage); 34 | 35 | /** 36 | * @brief Draws the main UI elements. 37 | * 38 | * @param button 1 - 5 for a button to be highlighted, or 0 if no buttons are highlighted. 39 | * @param totalLines Total number of lines in the current file. 40 | * @param startLine Line at the top of the current page. 41 | */ 42 | void ui_DrawUIMain(uint8_t button, unsigned int totalLines, unsigned int startLine); 43 | 44 | /** 45 | * @brief Draws a menu box. 46 | * 47 | * @param x X coordinate to start drawing the menu box at. 48 | * @param y Y coordinate to begin drawing the menu box at. 49 | * @param width Width of the menu box. 50 | * @param height Height of the menu box. 51 | * @param option Currently selected option. 52 | * @param optionCount Total number of options in the menu. 53 | * @param ... Option names, in order from top to bottom. 54 | */ 55 | void ui_DrawMenuBox(unsigned int x, uint8_t y, uint8_t width, uint8_t height, uint8_t option, unsigned int optionCount, ...); 56 | 57 | /** 58 | * @brief Main screen when no file is currently opened. 59 | * 60 | */ 61 | void ui_NoFile(void); 62 | 63 | /** 64 | * @brief Draws the cursor and highlights the currently selected row. 65 | * 66 | * @param row Currently selected row. 67 | * @param column Currently selected column. 68 | * @param cursorActive Whether or not the cursor is active (used for blinking animation). 69 | * @param erase If true, erase the footprint of the cursor and line highlight. 70 | */ 71 | void ui_DrawCursor(uint8_t row, uint8_t column, bool cursorActive, bool erase); 72 | 73 | /** 74 | * @brief Updates text currently on screen. 75 | * 76 | * @param studioContext eZ80 Studio context struct. 77 | * @param studioPreferences eZ80 Studio preferences struct. 78 | * @param drawMode 0 - update all text, 1 - update only top row, 2 - update only bottom row. 79 | */ 80 | void ui_UpdateText(struct context_t *studioContext, struct preferences_t *studioPreferences, uint8_t drawMode); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/highlight.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - highlight.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "highlight.h" 13 | #include "defines.h" 14 | #include "asm/lexer.h" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | char *hlight_GetTokenString(char *string, char *stringEnd) { 21 | static char stringConvert[MAX_TOK_LENGTH_HL]; 22 | 23 | for (uint8_t i = 0; i < stringEnd - string; i++) { // Convert caps to lowercase 24 | stringConvert[i] = string[i]; 25 | 26 | if (stringConvert[i] >= 'A' && stringConvert[i] <= 'Z') { 27 | stringConvert[i] += 'a' - 'A'; 28 | } 29 | } 30 | 31 | stringConvert[stringEnd - string] = '\0'; 32 | 33 | return stringConvert; 34 | } 35 | 36 | bool hlight_Number(char *string, char *stringEnd) { 37 | uint8_t numberType = NUMBER_DEC; 38 | 39 | if (*string == '$') { 40 | numberType = NUMBER_HEX; 41 | } else if (*string == '0' && *(string + 1) == 'x') { 42 | numberType = NUMBER_HEX; 43 | string++; 44 | } else if (*string == '@') { 45 | numberType = NUMBER_OCT; 46 | } else if (*string == '%') { 47 | numberType = NUMBER_BIN; 48 | } else { 49 | string--; 50 | } 51 | 52 | string++; 53 | 54 | if (string == stringEnd && numberType != NUMBER_DEC) { 55 | return false; 56 | } 57 | 58 | while (string != stringEnd) { 59 | if (numberType == NUMBER_HEX && 60 | !((*string >= '0' && *string <= '9') || 61 | (*string >= 'A' && *string <= 'F') || 62 | (*string >= 'a' && *string <= 'f'))) { 63 | 64 | return false; // Not a valid number 65 | } else if (numberType == NUMBER_DEC && (*string < '0' || *string > '9')) { 66 | return false; 67 | } else if (numberType == NUMBER_OCT && (*string < '0' || *string > '7')) { 68 | return false; 69 | } else if (numberType == NUMBER_BIN && (*string < '0' || *string > '1')) { 70 | return false; 71 | } 72 | 73 | string++; 74 | } 75 | 76 | return true; // Number is valid 77 | } 78 | 79 | uint8_t hlight_GetHighlightColor(char *string, char *stringEnd, bool highlighting) { 80 | if (!highlighting) { 81 | return TEXT_DEFAULT; 82 | } 83 | 84 | if (*(stringEnd - 1) == ':' && stringEnd - 1 != string) { 85 | return TEXT_LABEL; 86 | } 87 | 88 | if ((*string >= 'A' && *string <= 'z') || *string == '.') { 89 | uint8_t highlightColor = asm_lexer_TokType(string, stringEnd); 90 | 91 | if (highlightColor) { 92 | return (highlightColor == TEXT_MODIFIER) ? TEXT_INSTRUCTION : highlightColor; 93 | } 94 | } 95 | 96 | if ((*string >= '0' && *string <= '9') || *string == '$' || *string == '@' || *string == '%') { 97 | if (hlight_Number(string, stringEnd)) { 98 | return TEXT_NUMBER; 99 | } 100 | } else if (*string == '(' || *string == ')') { 101 | return TEXT_PARENTHESIS; 102 | } 103 | 104 | return TEXT_DEFAULT; 105 | } 106 | -------------------------------------------------------------------------------- /src/asm/spi.asm: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - spi.asm 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Some code from: https://github.com/Zaalan3/AnotherWorldCE/blob/main/src/spi.asm 6 | ; Copyright 2022 - 2025 7 | ; License: GPL-3.0 8 | ; 9 | ;-------------------------------------- 10 | 11 | assume adl=1 12 | 13 | section .text 14 | 15 | include 'include/equates.inc' 16 | 17 | public _asm_spi_BeginFrame 18 | public _asm_spi_EndFrame 19 | public _asm_spi_SetupSPI 20 | 21 | ;-------------------------------------- 22 | 23 | macro spi cmd, params& 24 | ld a, cmd 25 | call asm_spi_SpiCmd 26 | match any, params 27 | iterate param, any 28 | ld a, param 29 | call asm_spi_SpiParam 30 | end iterate 31 | end match 32 | end macro 33 | 34 | ;-------------------------------------- 35 | 36 | _asm_spi_BeginFrame: 37 | ld a, (ti.mpLcdRis) 38 | and a, ti.lcdIntVcomp 39 | jr z, _asm_spi_BeginFrame 40 | ld (ti.mpLcdIcr), a 41 | spi $B0, $01 ; disable framebuffer copies 42 | spi $2C 43 | ret 44 | 45 | _asm_spi_EndFrame: 46 | ld a, (ti.mpLcdCurr + 2) ; a = *mpLcdCurr >> 16 47 | ld hl, (ti.mpLcdCurr + 1) ; hl = *mpLcdCurr >> 8 48 | sub a, h 49 | jr nz, _asm_spi_EndFrame ; nz ==> lcdCurr may have updated mid-read; retry read 50 | ld de, -ti.lcdWidth * ti.lcdHeight 51 | add hl, de 52 | ld de, (ti.mpLcdBase) 53 | or a, a 54 | sbc hl, de 55 | or a, a 56 | sbc hl, de 57 | jr z, .resetVcomp 58 | ld a, ti.lcdIntVcomp 59 | ld (ti.mpLcdIcr), a 60 | 61 | .loop: 62 | ld a, (ti.mpLcdRis) 63 | bit ti.bLcdIntVcomp, a 64 | jr z, .loop 65 | 66 | .resetVcomp: 67 | ld a, ti.lcdIntVcomp 68 | ld (ti.mpLcdIcr), a 69 | spi $B0, $11 ; enable framebuffer copies 70 | ret 71 | 72 | _asm_spi_SetupSPI: ; set these defaults for the SPI so everything works on Python models (this seems to work instead of using boot.InitializeHardware) 73 | ld hl, $2000B 74 | ld (ti.mpSpiRange + ti.spiCtrl1), hl 75 | ld hl, $1828 76 | ld (ti.mpSpiRange), hl 77 | ld hl, $0C 78 | ld (ti.mpSpiRange + ti.spiCtrl2), hl 79 | nop 80 | ld hl, $40 81 | ld (ti.mpSpiRange + ti.spiCtrl2), hl 82 | call ti.Delay10ms 83 | ld hl, $182B 84 | ld (ti.mpSpiRange), hl 85 | ld hl, $0C 86 | ld (ti.mpSpiRange + ti.spiCtrl2), hl 87 | nop 88 | ld hl, $40 89 | ld (ti.mpSpiRange + ti.spiCtrl2), hl 90 | call ti.Delay10ms 91 | ld hl, $21 92 | ld (ti.mpSpiRange + ti.spiIntCtrl), hl 93 | ld hl, $100 94 | ld (ti.mpSpiRange + ti.spiCtrl2), hl 95 | ret 96 | 97 | ;-------------------------------------- 98 | 99 | asm_spi_SpiParam: 100 | scf 101 | virtual 102 | jr nc, $ 103 | load .jr_nc : byte from $$ 104 | end virtual 105 | db .jr_nc 106 | 107 | asm_spi_SpiCmd: 108 | or a, a 109 | ld hl, ti.mpSpiData or spiValid shl 8 110 | ld b, 3 111 | 112 | .loop: 113 | rla 114 | rla 115 | rla 116 | ld (hl), a 117 | djnz .loop 118 | ld l, h 119 | ld (hl), 1 120 | 121 | .wait: 122 | ld l, ti.spiStatus + 1 123 | 124 | .wait1: 125 | ld a, (hl) 126 | and a, $F0 127 | jr nz, .wait1 128 | dec l 129 | 130 | .wait2: 131 | bit 2, (hl) 132 | jr nz, .wait2 133 | ld l, h 134 | ld (hl), a 135 | ret 136 | -------------------------------------------------------------------------------- /src/utility.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - utility.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef UTILITY_H 13 | #define UTILITY_H 14 | 15 | #include "defines.h" 16 | 17 | #include 18 | #include 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | /** 25 | * @brief Read preferences from the AppVar, or set the default preferences if the AppVar doesn't exist. 26 | * 27 | * @param studioPreferences eZ80 Studio preferences struct. 28 | */ 29 | void util_ReadPrefs(struct preferences_t *studioPreferences); 30 | 31 | /** 32 | * @brief Write preferences to AppVar. 33 | * 34 | * @param studioPreferences eZ80 Studio preferences struct. 35 | */ 36 | void util_WritePrefs(struct preferences_t *studioPreferences); 37 | 38 | /** 39 | * @brief Gets a list of openable files into os_PixelShadow. 40 | * 41 | * @param fileCount Pointer to an int holding the number of openable files. 42 | * @param header Header string to search for. 43 | */ 44 | void util_GetFiles(unsigned int *fileCount, char *header); 45 | 46 | /** 47 | * @brief Returns a pointer to the end of a token. 48 | * 49 | * @param string String to search. 50 | * @param openEOF End of the currently opened file. 51 | * @param parser Whether or not the parser is active. 52 | * @return char* Pointer to the end of the token. 53 | */ 54 | char *util_GetStringEnd(char *string, char *openEOF, bool parser); 55 | 56 | /** 57 | * @brief Returns a pointer to the start of a token. 58 | * 59 | * @param string String to search. 60 | * @return char* Pointer to the start of the token. 61 | */ 62 | char *util_GetStringStart(char *string); 63 | 64 | /** 65 | * @brief Inserts a character at the current cursor location in the currently opened file. 66 | * 67 | * @param character Character to insert. 68 | * @param studioContext eZ80 Studio context struct. 69 | * @return true Character inserted succesfully. 70 | * @return false Character was not inserted. 71 | */ 72 | bool util_InsertChar(char character, struct context_t *studioContext); 73 | 74 | /** 75 | * @brief Displays a box asking for an input. 76 | * 77 | * @param x X coordinate to begin drawing input box at. 78 | * @param y Y coordinate to begin drawing input box at. 79 | * @param stringLength Maximum length of input string. 80 | * @param inputMode Current input mode. 81 | * @param exitKey Key to press to exit the input box. 82 | * @return char* String typed into input box. 83 | */ 84 | char *util_StringInputBox(unsigned int x, uint8_t y, uint8_t stringLength, uint8_t inputMode, kb_lkey_t exitKey); 85 | 86 | /** 87 | * @brief Waits before repeating a keypress. 88 | * 89 | * @param clockOffset Clock offset for timer. 90 | * @param keyPressed Whether a key is currently pressed. 91 | */ 92 | void util_WaitBeforeKeypress(clock_t *clockOffset, bool *keyPressed); 93 | 94 | /** 95 | * @brief Opens a file and sets the context accordingly. 96 | * 97 | * @param studioContext eZ80 Studio context struct. 98 | * @param fileName Name of the file to open. 99 | * @return true File opened successfully. 100 | * @return false File was not opened. 101 | */ 102 | bool util_OpenFile(struct context_t *studioContext, char *fileName); 103 | 104 | #ifdef __cplusplus 105 | } 106 | #endif 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - main.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * Last Build: October 25, 2025 9 | * Version: 1.1.0 10 | * 11 | * -------------------------------------- 12 | **/ 13 | 14 | #include "gfx/gfx.h" 15 | #include "font/fontCherry.h" 16 | #include "defines.h" 17 | #include "edit.h" 18 | #include "menu.h" 19 | #include "parser.h" 20 | #include "ui.h" 21 | #include "utility.h" 22 | #include "asm/files.h" 23 | #include "asm/spi.h" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | int main(void) { 30 | static struct preferences_t studioPreferences; 31 | static struct context_t studioContext; 32 | 33 | studioContext.fileIsOpen = false; 34 | studioContext.fileIsSaved = true; 35 | 36 | studioContext.lineStart = 0; // The scrollbar will be weird if we don't do this 37 | studioContext.newlineStart = 0; 38 | studioContext.totalLines = 0; 39 | studioContext.newlineCount = 0; 40 | studioContext.inputMode = INPUT_LOWERCASE; 41 | 42 | util_ReadPrefs(&studioPreferences); 43 | 44 | fontlib_SetFont(fontCherry, 0); 45 | fontlib_SetForegroundColor(TEXT_DEFAULT); 46 | fontlib_SetNewlineOptions(1); 47 | fontlib_SetTransparency(true); 48 | 49 | asm_spi_SetupSPI(); 50 | gfx_Begin(); 51 | 52 | if (studioPreferences.theme) { 53 | gfx_SetPalette(darkPalette, sizeof_darkPalette, 0); 54 | } else { 55 | gfx_SetPalette(lightPalette, sizeof_lightPalette, 0); 56 | } 57 | 58 | edit_RedrawEditor(&studioContext, &studioPreferences); 59 | 60 | while (kb_AnyKey()); 61 | 62 | while (!kb_IsDown(kb_KeyClear)) { 63 | kb_Scan(); 64 | 65 | if (kb_IsDown(kb_KeyYequ)) { 66 | menu_File(&studioContext, &studioPreferences); 67 | edit_RedrawEditor(&studioContext, &studioPreferences); 68 | while (kb_AnyKey()); 69 | } else if (kb_IsDown(kb_KeyWindow)) { 70 | while (kb_AnyKey()); 71 | } else if (kb_IsDown(kb_KeyZoom)) { 72 | menu_Goto(&studioContext); 73 | edit_RedrawEditor(&studioContext, &studioPreferences); 74 | while (kb_AnyKey()); 75 | } else if (kb_IsDown(kb_KeyTrace)) { 76 | char insert = menu_Chars(&studioContext); 77 | 78 | if (studioContext.fileSize < MAX_FILE_SIZE && studioContext.fileIsOpen) { 79 | util_InsertChar(insert, &studioContext); 80 | } 81 | 82 | edit_RedrawEditor(&studioContext, &studioPreferences); 83 | while (kb_AnyKey()); 84 | } else if (kb_IsDown(kb_KeyGraph)) { 85 | menu_Settings(&studioContext, &studioPreferences); 86 | edit_RedrawEditor(&studioContext, &studioPreferences); 87 | while (kb_AnyKey()); 88 | } 89 | 90 | if (studioContext.fileIsOpen) { 91 | edit_OpenEditor(&studioContext, &studioPreferences); 92 | studioContext.fileIsOpen = false; 93 | studioContext.fileIsSaved = true; 94 | studioContext.lineStart = 0; 95 | studioContext.newlineStart = 0; 96 | studioContext.totalLines = 0; 97 | studioContext.newlineCount = 0; 98 | edit_RedrawEditor(&studioContext, &studioPreferences); 99 | while (kb_AnyKey()); 100 | } 101 | } 102 | 103 | asm_spi_EndFrame(); // Do this to be safe 104 | gfx_End(); 105 | 106 | util_WritePrefs(&studioPreferences); 107 | 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /src/asm/files.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - files.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef FILES_H 13 | #define FILES_H 14 | 15 | #include 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Reads the data (including header) of a specified file into EDIT_BUFFER - 2. 23 | * 24 | * @param fileName File to read. 25 | * @return true Reading the file was successful. 26 | * @return false Reading the file was unsuccessful (the file didn't exist). 27 | */ 28 | bool asm_files_ReadFile(char *fileName); 29 | 30 | /** 31 | * @brief Writes the current data of the edit buffer into a specified file. 32 | * 33 | * @param fileName Name of the file to save to. 34 | * @param fileSize Total size of the data, including file header. 35 | * @return true Writing to the file was successful. 36 | * @return false Writing to the file was unsuccessful. 37 | */ 38 | bool asm_files_WriteFile(char *fileName, unsigned int fileSize); 39 | 40 | /** 41 | * @brief Check if a file with the specified name exists. 42 | * 43 | * @param name Name to check. 44 | * @param type File type. 45 | * @return true The file exists. 46 | * @return false The file doesn't exist. 47 | */ 48 | bool asm_files_CheckFileExists(char *name, uint8_t type); 49 | 50 | /** 51 | * @brief Counts the number of newlines and word-wrapped lines in the currently opened file. 52 | * 53 | * @param newlineCount Pointer to store newline count to. 54 | * @param lineCount Pointer to store word-wrapped line count to. 55 | * @param openEOF Pointer to the last byte of data in the edit buffer. 56 | */ 57 | void asm_files_CountLines(unsigned int *newlineCount, unsigned int *lineCount, char *openEOF); 58 | 59 | /** 60 | * @brief Get the number of characters in a word-wrapped line beginning at a specified pointer. 61 | * 62 | * @param rowDataStart Pointer to beginning of line. 63 | * @param openEOF Pointer to the last byte of data in the edit buffer. 64 | * @return uint8_t Number of characters (maximum 38 because more would wrap to the next line). 65 | */ 66 | uint8_t asm_files_GetLineLength(char *rowDataStart, char *openEOF); 67 | 68 | /** 69 | * @brief Get the number of spaces at the start of a line 70 | * 71 | * @param rowDataStart Pointer to beginning of line. 72 | * @param openEOF Pointer to the last byte of data in the edit buffer. 73 | * @return uint8_t Number of spaces (maximum 38 because more would wrap to the next line). 74 | */ 75 | uint8_t asm_files_GetStartSpacesNumber(char *rowDataStart, char *openEOF); 76 | 77 | /** 78 | * @brief Gets a pointer to the beginning of the next word-wrapped line. 79 | * 80 | * @param currentLine Pointer to the beginning byte of the current line. 81 | * @return char* Pointer to the beginning of the next line. 82 | */ 83 | char *asm_files_NextLine(char *currentLine); 84 | 85 | /** 86 | * @brief Gets a pointer to the beginning of the previous word-wrapped line. 87 | * 88 | * @param currentLine Pointer to the beginning byte of the current line. 89 | * @param openEOF Pointer to the last byte of data in the edit buffer. 90 | * @return char* Pointer to the beginning of the previous line. 91 | */ 92 | char *asm_files_PreviousLine(char *currentLine, char *openEOF); 93 | 94 | /** 95 | * @brief Inserts a character into the edit buffer, and shifts the data after it one byte over to make room. 96 | * 97 | * @param character Character to insert. 98 | * @param openEOF Pointer to the last byte of data in the edit buffer. 99 | * @param copySize Number of bytes to copy after the character being inserted. 100 | */ 101 | void asm_files_InsertChar(char character, char *openEOF, unsigned int copySize); 102 | 103 | /** 104 | * @brief Deletes a character in the edit buffer, and shifts the data after it one byte over to replace it. 105 | * 106 | * @param delete Pointer to character to delete. 107 | * @param copySize Number of bytes to copy after the character being deleted. 108 | */ 109 | void asm_files_DeleteChar(char *delete, unsigned int copySize); 110 | 111 | /** 112 | * @brief Creates a protected program containing the data at ti.vRam. 113 | * 114 | * @param size Size of the program / data, including assembly header. 115 | * @param name Name of the program to create. 116 | * @return uint8_t Error code. 117 | */ 118 | uint8_t asm_files_CreateProg(uint16_t size, char *name); 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/asm/misc.asm: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - misc.asm 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Copyright 2022 - 2025 6 | ; License: GPL-3.0 7 | ; 8 | ;-------------------------------------- 9 | 10 | assume adl=1 11 | 12 | section .text 13 | 14 | include 'include/equates.inc' 15 | 16 | public _asm_misc_FindOpcode 17 | public _asm_misc_StringToInt 18 | public _asm_misc_ClearBuffer 19 | public _asm_misc_GetCharFromKey 20 | public _asm_misc_ReverseCopy 21 | public _asm_misc_FindSymbol 22 | 23 | extern _asm_opcodes_Table 24 | extern _asm_opcodes_TableEnd 25 | extern _rodata_characters 26 | extern _rodata_sizeOfCharsLUT 27 | 28 | _asm_misc_FindOpcode: 29 | pop de 30 | ex (sp), hl 31 | push de 32 | ld de, _asm_opcodes_Table 33 | ex de, hl 34 | 35 | .search: 36 | push de 37 | push hl 38 | ld bc, 0 39 | ld c, (hl) 40 | add hl, bc 41 | inc hl 42 | 43 | .compare: 44 | ld a, (hl) 45 | or a, a 46 | jr nz, .check 47 | ld a, (de) 48 | or a, a 49 | jr z, .found 50 | pop de 51 | pop de 52 | inc hl 53 | jr .search 54 | 55 | .check: 56 | ld a, (de) 57 | cp a, (hl) 58 | inc hl 59 | inc de 60 | jr z, .compare 61 | ld de, _asm_opcodes_TableEnd 62 | or a, a 63 | sbc hl, de 64 | jr nc, .notFound 65 | add hl, de 66 | pop de 67 | pop de 68 | xor a, a 69 | ld bc, 16 70 | cpir 71 | jr .search 72 | 73 | .found: 74 | pop hl 75 | pop de 76 | ret 77 | 78 | .notFound: 79 | pop de 80 | pop de 81 | or a, a 82 | sbc hl, hl 83 | ret 84 | 85 | _asm_misc_StringToInt: 86 | pop de 87 | ex (sp), hl ; beginning of string 88 | push de 89 | ex de, hl 90 | ld hl, 0 91 | ld a, (de) 92 | or a, a 93 | ret z 94 | 95 | .loop: 96 | cp a, '0' 97 | jr c, .notANumber 98 | cp a, '9' + 1 99 | jr nc, .notANumber 100 | sub a, '0' 101 | ld bc, 0 102 | ld c, a 103 | add hl, bc 104 | inc de 105 | ld a, (de) 106 | or a, a 107 | ret z 108 | add hl, hl 109 | push hl 110 | pop bc 111 | add hl, hl 112 | add hl, hl 113 | add hl, bc 114 | jr .loop 115 | 116 | .notANumber: 117 | ld hl, -1 118 | ret 119 | 120 | _asm_misc_ClearBuffer: 121 | pop de 122 | ex (sp), hl ; get address to clear 123 | push de 124 | ld bc, ti.lcdWidth * ti.lcdHeight 125 | jp ti.MemClear 126 | 127 | _asm_misc_GetCharFromKey: ; Scans for a keypress and converts it to a character 128 | di 129 | ld hl, $F50200 130 | ld (hl), h 131 | xor a, a 132 | 133 | .loop: 134 | cp a, (hl) 135 | jr nz, .loop 136 | ld hl, ti.mpKeyRange + ti.keyData 137 | ld bc, 56 shl 8 138 | 139 | .getKeyLoop: 140 | ld a, b 141 | and a, 7 142 | jr nz, .sameGroup 143 | inc hl 144 | inc hl 145 | ld e, (hl) 146 | 147 | .sameGroup: 148 | sla e 149 | jr nc, .loopCode 150 | xor a, a 151 | cp a, c 152 | jr nz, .return 153 | ld c, b 154 | 155 | .loopCode: 156 | djnz .getKeyLoop 157 | 158 | .return: 159 | pop de 160 | ex (sp), hl 161 | push de 162 | ld h, _rodata_sizeOfCharsLUT 163 | mlt hl 164 | ld de, _rodata_characters 165 | add hl, de 166 | ld a, c 167 | sub a, 9 168 | jr c, $ + 8 169 | cp a, _rodata_sizeOfCharsLUT + 1 170 | jr nc, $ + 4 171 | ld c, a 172 | add hl, bc 173 | ld a, (hl) 174 | ret 175 | 176 | _asm_misc_ReverseCopy: 177 | ld iy, 0 178 | add iy, sp 179 | ld hl, (iy + 3) 180 | ld de, (iy + 6) 181 | ld bc, (iy + 9) 182 | lddr 183 | ret 184 | 185 | _asm_misc_FindSymbol: 186 | ld iy, 0 187 | add iy, sp 188 | ld de, (iy + 3) 189 | ld hl, (iy + 6) 190 | 191 | .search: 192 | push de 193 | push hl 194 | 195 | .compare: 196 | ld a, (hl) 197 | or a, a 198 | jr nz, .check 199 | ld a, (de) 200 | or a, a 201 | jr z, .found 202 | pop de 203 | pop de 204 | inc hl 205 | jr .skipData 206 | 207 | .check: 208 | ld a, (de) 209 | cp a, (hl) 210 | inc hl 211 | inc de 212 | jr z, .compare 213 | pop de 214 | pop de 215 | xor a, a 216 | ld bc, 256 217 | cpir 218 | 219 | .skipData: 220 | ld bc, 0 221 | ld c, (hl) 222 | add hl, bc 223 | inc hl 224 | ld a, (hl) 225 | or a, a ; reached the end of the table's entries 226 | jr z, .notFound 227 | jr .search 228 | 229 | .found: 230 | pop hl 231 | pop de 232 | ret 233 | 234 | .notFound: 235 | or a, a 236 | sbc hl, hl 237 | ret 238 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Introduction 2 | -------------------- 3 | 4 | eZ80 Studio is an on-calc eZ80 assembly IDE for the TI-84 Plus CE and TI-83 Premium CE calculators. 5 | It includes an editor, assembler, and include file support. 6 | 7 | Features 8 | -------------------- 9 | 10 | * Editor with word wrap and up to 14 lines and 38 columns on screen at once. 11 | * Toggleable syntax highlighting. 12 | * Light and dark themes. 13 | * Special characters. 14 | * Quick jump to lines. 15 | * Built-in eZ80 assembler, with support for data and reserve macros. 16 | * Support for `$` symbol to represent the current address when assembling. 17 | 18 | Installation 19 | -------------------- 20 | 21 | 1. Download the latest version of eZ80 Studio from the GitHub releases page: 22 | https://github.com/EzCE/ez80-studio/releases/latest 23 | 2. Send EZSTUDIO.8xp along with any include files (Found in includes/*.8xv) to your calculator using 24 | TI-Connect CE or TiLP. If you don't have the [CE C libraries](https://tiny.cc/clibs), you'll need 25 | to download and send those as well. 26 | 3. Run **prgmEZSTUDIO** from the programs menu. You will need to use the arTIfiCE jailbreak 27 | (https://yvantt.github.io/arTIfiCE) if you are on an OS version 5.5 and above. 28 | 29 | Navigation 30 | -------------------- 31 | 32 | Below is a table with keys and their various usage in eZ80 Studio: 33 | 34 | +--------------------+---------------------------------------------------------------+ 35 | | Key | Action performed | 36 | +--------------------+---------------------------------------------------------------+ 37 | | [2nd] / [enter] | Select options from menus or insert a newline. | 38 | | [↑], [↓], [←], [→] | Scroll through menus or move the cursor in the editor. | 39 | | [y=] | Open/exit files menu. | 40 | | [window] | Assemble the currently opened file. | 41 | | [zoom] | Open/exit goto line menu. | 42 | | [trace] | Open/exit special characters menu. | 43 | | [graph] | Open/exit settings menu. | 44 | | [del] | Delete the character in front of the cursor. | 45 | | [mode] | Backspace. | 46 | | [alpha] | Switch between uppercase, lowercase, and numeric input modes. | 47 | | [clear] | Exit the program or close the current menu or window. | 48 | +--------------------+---------------------------------------------------------------+ 49 | 50 | Include files 51 | -------------------- 52 | 53 | Five include files are provided with eZ80 Studio, though you can also convert your own. These 54 | include files are based on ti84pceg.inc and the include files provided with BASM-3 55 | (https://github.com/beckadamtheinventor/BASM-3/tree/master/data). TIOSRTNS.8xv contains system 56 | routines, TIOSRAMA.8xv contains RAM areas, TIOSFLAG.8xv contains system flags, TIOSKBEQ.8xv contains 57 | keyboard and context equates, and TI84PCEG_smaller.8xv is a slightly downsized version of the full 58 | ti84pceg.inc (which is too large to fit on the calculator in one file). TI84PCEG_smaller.8xv 59 | contains the majority of the other four include files, so it unlikely you would need both the four 60 | smaller includes as well. 61 | 62 | Converting files 63 | -------------------- 64 | 65 | Converting include or source files from the computer to a file compatible with eZ80 Studio can be 66 | done quickly using the provided conversion scripts. 67 | 68 | To convert source files, use convert.sh. If it is not already installed, you will also need convbin, 69 | which can be found here: https://github.com/mateoconlechuga/convbin 70 | The syntax for convert.sh is `convert.sh `, where `` is 71 | the name of the file to convert, `` is the name of the output file to generate when 72 | converting, and `` is either `var` to convert an eZ80 Studio AppVar to an assembly source file 73 | or `asm` to convert an assembly source file to an eZ80 Studio AppVar editable on the calculator. 74 | 75 | To convert include files, use includes/conv-inc.py. If it is not already installed, you will also 76 | need fasmg (https://flatassembler.net/download.php). Running the python program will prompt for the 77 | name of the file to convert and should output the converted file to includes/bin. 78 | 79 | Bugs 80 | -------------------- 81 | 82 | If you encounter a bug while using eZ80 Studio, don't hesitate to make an issue on GitHub or report 83 | it on the Discord server (https://discord.gg/RDTtu258fW). Feel free to request features or ask for 84 | help on the discord or in the Cemetech topic (https://ceme.tech/t19925) as well! 85 | 86 | Credits 87 | -------------------- 88 | 89 | Thanks to beckadamtheinventor (https://github.com/beckadamtheinventor/) for help with the assembler 90 | portion of the program and to Cemetech (https://cemetech.net) members for feedback! 91 | 92 | © 2022 - 2025 RoccoLox Programs and TIny_Hacker 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eZ80 Studio [![Discord Chat Link](https://img.shields.io/discord/1012426214226530424?logo=discord)](https://discord.gg/RDTtu258fW) [![Release](https://img.shields.io/github/v/release/EzCE/ez80-studio?include_prereleases)](https://github.com/EzCE/ez80-studio/releases/latest) 2 | 3 | eZ80 Studio is an on-calc eZ80 assembly IDE for the TI-84 Plus CE and TI-83 Premium CE calculators. It includes an editor, assembler, and include file support. 4 | 5 | ## Screenshots 6 | 7 | ![File editing and assembling](screenshots/ez80-studio.gif "File editing and assembling") ![Settings](screenshots/settings.gif "Settings") 8 | ![Other menus](screenshots/other-menus.gif "Other menus") ![About](screenshots/about.png "About") 9 | 10 | ## Features 11 | 12 | * Editor with word wrap and up to 14 lines and 38 columns on screen at once. 13 | * Toggleable syntax highlighting. 14 | * Light and dark themes. 15 | * Special characters. 16 | * Quick jump to lines. 17 | * Built-in eZ80 assembler, with support for data and reserve macros. 18 | * Support for `$` symbol to represent the current address when assembling. 19 | 20 | ## Installation 21 | 22 | 1. Download the latest version of eZ80 Studio from [the GitHub releases page](https://github.com/EzCE/ez80-studio/releases/latest). 23 | 2. Send **EZSTUDIO.8xp** along with any include files (Found in **includes/\*.8xv**) to your calculator using TI-Connect CE or TiLP. If you don't have the [CE C libraries](https://tiny.cc/clibs), you'll need to download and send those as well. 24 | 3. Run **prgmEZSTUDIO** from the programs menu (You will need to use the [arTIfiCE jailbreak](https://yvantt.github.io/arTIfiCE) if you are on an OS version 5.5 and above). 25 | 26 | ## Navigation 27 | 28 | Below is a table with keys and their various usage in eZ80 Studio: 29 | 30 | | Key | Action performed | 31 | |--------------------------------------------------------|---------------------------------------------------------------| 32 | | 2nd / enter | Select options from menus or insert a newline. | 33 | | , , , | Scroll through menus or move the cursor in the editor. | 34 | | y= | Open/exit files menu. | 35 | | window | Assemble the currently opened file. | 36 | | zoom | Open/exit goto line menu. | 37 | | trace | Open/exit special characters menu. | 38 | | graph | Open/exit settings menu. | 39 | | del | Delete the character in front of the cursor. | 40 | | mode | Backspace. | 41 | | alpha | Switch between uppercase, lowercase, and numeric input modes. | 42 | | clear | Exit the program or close the current menu or window. | 43 | 44 | ## Include files 45 | 46 | Five include files are provided with eZ80 Studio, though you can also [convert your own](#converting-files). These include files are based on **[ti84pceg.inc](https://github.com/CE-Programming/asm-docs/blob/master/programs/include/ti84pceg.inc)** and the include files provided with **[BASM-3](https://github.com/beckadamtheinventor/BASM-3/tree/master/data)**. **TIOSRTNS.8xv** contains system routines, **TIOSRAMA.8xv** contains RAM areas, **TIOSFLAG.8xv** contains system flags, **TIOSKBEQ.8xv** contains keyboard and context equates, and **TI84PCEG_smaller.8xv** is a slightly downsized version of the full **ti84pceg.inc** (which is too large to fit on the calculator in one file). **TI84PCEG_smaller.8xv** contains the majority of the other four include files, so it unlikely you would need both the four smaller includes as well. 47 | 48 | ## Converting files 49 | 50 | Converting include or source files from the computer to a file compatible with eZ80 Studio can be done quickly using the provided conversion scripts. 51 | 52 | To convert source files, use **convert.sh**. You will also need [convbin](https://github.com/mateoconlechuga/convbin) if it is not already installed. The syntax for **convert.sh** is `convert.sh `, where `` is the name of the file to convert, `` is the name of the output file to generate when converting, and `` is either `var` to convert an eZ80 Studio AppVar to an assembly source file or `asm` to convert an assembly source file to an eZ80 Studio AppVar editable on the calculator. 53 | 54 | To convert include files, use **includes/conv-inc.py**. You will also need [fasmg](https://flatassembler.net/download.php) if it is not already installed. Running the python program will prompt for the name of the file to convert and should output the converted file to **includes/bin**. 55 | 56 | ## Bugs 57 | 58 | If you encounter a bug while using eZ80 Studio, don't hesitate to [make an issue](https://github.com/EzCE/ez80-studio/issues) or report it on the [Discord server](https://discord.gg/RDTtu258fW). Feel free to request features or ask for help on the discord or in the [Cemetech topic](https://ceme.tech/t19925) as well! 59 | 60 | ## Credits 61 | 62 | Thanks to [beckadamtheinventor](https://github.com/beckadamtheinventor/) for help with the assembler portion of the program and to [Cemetech](https://cemetech.net) members for feedback! 63 | 64 | © 2022 - 2025 RoccoLox Programs and TIny_Hacker 65 | -------------------------------------------------------------------------------- /src/asm/sortVat.asm: -------------------------------------------------------------------------------- 1 | ; Copyright 2015-2021 Matt "MateoConLechuga" Waltz 2 | ; 3 | ; Redistribution and use in source and binary forms, with or without 4 | ; modification, are permitted provided that the following conditions are met: 5 | ; 6 | ; 1. Redistributions of source code must retain the above copyright notice, 7 | ; this list of conditions and the following disclaimer. 8 | ; 9 | ; 2. Redistributions in binary form must reproduce the above copyright notice, 10 | ; this list of conditions and the following disclaimer in the documentation 11 | ; and/or other materials provided with the distribution. 12 | ; 13 | ; 3. Neither the name of the copyright holder nor the names of its contributors 14 | ; may be used to endorse or promote products derived from this software 15 | ; without specific prior written permission. 16 | ; 17 | ; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | ; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | ; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | ; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | ; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | ; POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ; made by martin warmer, mmartin@xs4all.nl 30 | ; modified for eZ80 architecture and hidden programs by matt waltz 31 | ; 32 | ; uses insertion sort to sort the vat alphabetically 33 | 34 | assume adl=1 35 | 36 | section .text 37 | 38 | include 'include/equates.inc' 39 | 40 | public _asm_misc_SortVAT 41 | 42 | _asm_misc_SortVAT: 43 | ld iy, ti.flags 44 | or a, a 45 | sbc hl, hl 46 | ld (sortFirstItemFoundPtr), hl 47 | ld hl, (ti.progPtr) 48 | 49 | .sortNext: 50 | call .findNextItem 51 | ret nc 52 | 53 | .foundItem: 54 | push hl 55 | ld hl, (sortFirstItemFoundPtr) 56 | add hl, de 57 | or a, a 58 | sbc hl, de 59 | pop hl 60 | jr nz, .notFirst 61 | ld (sortFirstItemFoundPtr), hl ; to make it only execute once 62 | call .skipName 63 | ld (sortEndOfPartPtr), hl 64 | jr .sortNext 65 | 66 | .notFirst: 67 | push hl 68 | call .skipName 69 | pop de 70 | push hl ; to continue from later on 71 | ld hl, (sortFirstItemFoundPtr) 72 | jr .searchNextStart ; could speed up sorted list by first checking if it's the last item (not neccessary) 73 | 74 | .searchNext: 75 | call .skipName 76 | ld bc, (sortEndOfPartPtr) 77 | or a, a ; reset carry flag 78 | push hl 79 | sbc hl, bc 80 | pop hl 81 | jr z, .locationFound 82 | ld bc, -6 83 | add hl, bc 84 | 85 | .searchNextStart: 86 | push hl 87 | push de 88 | call .compareNames 89 | pop de 90 | pop hl 91 | jr nc, .searchNext 92 | 93 | .searchNextEnd: 94 | ld bc, 6 95 | add hl, bc ; goto start of entry 96 | 97 | .locationFound: 98 | ex de, hl 99 | ld a, (hl) 100 | add a, 7 101 | ld bc, 6 ; rewind six bytes 102 | add hl, bc ; a = number of bytes to move 103 | ld c, a ; hl -> bytes to move 104 | ld (sortVatEntrySize), bc ; de -> move to location 105 | ld (sortVatEntryNewLoc), de 106 | push de 107 | push hl 108 | or a, a 109 | sbc hl, de 110 | pop hl 111 | pop de 112 | jr z, .noMoveNeeded 113 | push hl 114 | ld de, sortVatEntryTempEnd 115 | lddr ; copy entry to move to sortVatEntryTempEnd 116 | ld hl, (sortVatEntryNewLoc) 117 | pop bc 118 | push bc 119 | or a, a 120 | sbc hl, bc 121 | push hl 122 | pop bc 123 | pop hl 124 | inc hl 125 | push hl 126 | ld de, (sortVatEntrySize) 127 | or a, a 128 | sbc hl, de 129 | ex de, hl 130 | pop hl 131 | ldir 132 | ld hl, sortVatEntryTempEnd 133 | ld bc, (sortVatEntrySize) 134 | ld de, (sortVatEntryNewLoc) 135 | lddr 136 | ld hl, (sortEndOfPartPtr) 137 | ld bc, (sortVatEntrySize) 138 | or a, a 139 | sbc hl, bc 140 | ld (sortEndOfPartPtr), hl 141 | pop hl ; pointer to continue from 142 | jp .sortNext ; to skip name and rest of entry 143 | 144 | .noMoveNeeded: 145 | pop hl 146 | ld (sortEndOfPartPtr), hl 147 | jp .sortNext 148 | 149 | .skipToNext: 150 | ld bc, -6 151 | add hl, bc 152 | call .skipName 153 | jr .findNextItem ; look for next item 154 | 155 | .skipName: 156 | ld bc, 0 157 | ld c, (hl) ; number of bytes in name 158 | inc c ; to get pointer to data type byte of next entry 159 | or a, a ; reset carry flag 160 | sbc hl, bc 161 | ret 162 | 163 | .compareNames: ; hl and de pointers to strings output=carry if de is first 164 | ld b, (hl) 165 | ld a, (de) 166 | ld c, 0 167 | cp a, b ; check if same length 168 | jr z, .hlLonger 169 | jr nc, .hlLonger ; b = smaller than a 170 | inc c ; to remember that b was larger 171 | ld b, a ; b was larger than a 172 | 173 | .hlLonger: 174 | push bc 175 | ld b, 64 176 | dec hl 177 | dec de 178 | ld a, (hl) 179 | cp a, b 180 | jr nc, .firstNotHidden ; check if files are hidden 181 | add a, b 182 | 183 | .firstNotHidden: 184 | ld c, a 185 | ld a, (de) 186 | cp a, b 187 | jr nc, .secondNotHidden 188 | add a, b 189 | 190 | .secondNotHidden: 191 | cp a, c 192 | pop bc 193 | jr .start 194 | 195 | .loop: 196 | dec hl 197 | dec de 198 | ld a, (de) 199 | cp a, (hl) 200 | 201 | .start: 202 | ret nz 203 | djnz .loop 204 | dec c 205 | ret nz 206 | ccf 207 | ret 208 | 209 | .findNextItem: ; carry = found, nc = notfound 210 | ex de, hl 211 | ld hl, (ti.pTemp) 212 | or a, a ; reset carry flag 213 | sbc hl, de 214 | ret z 215 | ex de, hl ; load progptr into hl 216 | ld a, (hl) 217 | and a, $1F ; mask out state bytes 218 | push hl 219 | ld hl, misc_sortTypes 220 | ld bc, misc_sortTypes.length 221 | cpir 222 | pop hl 223 | jr nz, .skipToNext ; skip to next entry 224 | dec hl ; add check for folders here if needed 225 | dec hl 226 | dec hl ; to pointer 227 | ld e, (hl) 228 | dec hl 229 | ld d, (hl) ; pointer now in de 230 | dec hl 231 | ld a, (hl) ; high byte now in a 232 | dec hl ; add check: do I need to sort this program (not necessary) 233 | scf 234 | ret 235 | 236 | misc_sortTypes: 237 | db ti.ProgObj, ti.ProtProgObj, ti.AppVarObj 238 | .length := $-. 239 | -------------------------------------------------------------------------------- /src/defines.h: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - defines.h 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #ifndef DEFINES_H 13 | #define DEFINES_H 14 | 15 | #include 16 | #include 17 | 18 | 19 | #include 20 | 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | /** 27 | * @brief PixelShadow RAM location. 28 | * 29 | */ 30 | #define os_PixelShadow ((uint8_t *)0xD031F6) 31 | 32 | /** 33 | * @brief userMem RAM location. 34 | * 35 | */ 36 | #define os_userMem ((uint8_t *)0xD1A881) 37 | 38 | /** 39 | * Opcode Map - Second Opcode (after $CB) 40 | */ 41 | extern struct opcode_t asm_opcodes_AfterCB; 42 | 43 | /** 44 | * Opcode Map - Second Opcode (after $DD) 45 | */ 46 | extern struct opcode_t asm_opcodes_AfterDD; 47 | 48 | /** 49 | * Opcode Map - Second Opcode (after $ED) 50 | */ 51 | extern struct opcode_t asm_opcodes_AfterED; 52 | 53 | /** 54 | * Opcode Map - Second Opcode (after $FD) 55 | */ 56 | extern struct opcode_t asm_opcodes_AfterFD; 57 | 58 | /** 59 | * Opcode Map - Second Opcode (after $DDCB##) 60 | */ 61 | extern struct opcode_t asm_opcodes_AfterDDCB; 62 | 63 | /** 64 | * Opcode Map - Second Opcode (after $FDCB##) 65 | */ 66 | extern struct opcode_t asm_opcodes_AfterFDCB; 67 | 68 | /** 69 | * End of Opcode Map. 70 | */ 71 | extern struct opcode_t asm_opcodes_TableEnd; 72 | 73 | /** 74 | * File header for valid source files: 0xEF7A (Also uncompiled assembly header for TI-BASIC programs). 75 | */ 76 | #define SOURCE_HEADER "\xEF\x7A" 77 | 78 | /** 79 | * File header for output: 0xEF7B. 80 | */ 81 | #define OUTPUT_HEADER "\xEF\x7B" 82 | 83 | /** 84 | * File header for includes (BASM compatible). 85 | */ 86 | #define INCLUDE_HEADER "eZ80 INC" 87 | 88 | /** 89 | * Palette entries (Also used for token types when highlighting because why not, they're already here). 90 | */ 91 | #define BACKGROUND 0 /* Background color. */ 92 | #define OUTLINE 1 /* Outlines of dialog boxes, buttons, etc. */ 93 | #define CURSOR 2 /* Cursor color, also used for pressed buttons. */ 94 | #define TEXT_DEFAULT 3 /* Default text color. */ 95 | #define TEXT_LABEL 4 /* Text color for labels. */ 96 | #define TEXT_INSTRUCTION 5 /* Text color for instructions. */ 97 | #define TEXT_REGISTER 6 /* Text color for registers. */ 98 | #define TEXT_NUMBER 7 /* Text color for numbers. */ 99 | #define TEXT_PARENTHESIS 8 /* Text color for parenthesis. */ 100 | #define TEXT_STRING 9 /* Text color for strings. */ 101 | #define TEXT_COMMENT 10 /* Text color for comments. */ 102 | #define TEXT_MODIFIER 11 /* Text color for suffixes. */ 103 | 104 | /** 105 | * Number types, used by the highlighting code. 106 | */ 107 | #define NUMBER_HEX 16 /* Hexadecimal number. */ 108 | #define NUMBER_DEC 10 /* Decimal number. */ 109 | #define NUMBER_OCT 8 /* Octal number. */ 110 | #define NUMBER_BIN 2 /* Binary number. */ 111 | 112 | /** 113 | * Suffix types. 114 | */ 115 | #define NO_SUFFIX 0 /* No suffix for the instruction. */ 116 | #define SUFFIX_SIS 1 /* Instruction has the SIS suffix. */ 117 | #define SUFFIX_SIL 2 /* Instruction has the SIL suffix. */ 118 | #define SUFFIX_LIS 3 /* Instruction has the LIS suffix. */ 119 | #define SUFFIX_LIL 4 /* Instruction has the LIL suffix. */ 120 | 121 | /** 122 | * GUI color themes. 123 | */ 124 | #define LIGHT_THEME true /* Light GUI color theme. */ 125 | #define DARK_THEME false /* Dark GUI color theme. */ 126 | 127 | /** 128 | * Text input modes. 129 | */ 130 | #define INPUT_NUMBERS 0 /* Numbers / symbols keypad layout. */ 131 | #define INPUT_UPPERCASE 1 /* Uppercase letters keypad layout. */ 132 | #define INPUT_LOWERCASE 2 /* Lowercase letters keypad layout. */ 133 | 134 | /** 135 | * Maximum file size. 65500 bytes to be safe, does not include header. 136 | */ 137 | #define MAX_FILE_SIZE 65500 138 | 139 | /** 140 | * Maximum symbol table size. 141 | */ 142 | #define MAX_SYMBOL_TABLE 11000 143 | 144 | /** 145 | * Error codes used in the editor and assembler. 146 | */ 147 | #define ERROR_SUCCESS 0 /* The action was completed succesfully. */ 148 | #define ERROR_UNKNOWN 1 /* The action failed for an unknown reason. (Not sure if this will be used?) */ 149 | #define ERROR_NO_MEM 2 /* There is not enough memory to perform the requested action. */ 150 | #define ERROR_INVAL_TOK 3 /* A token was invalid. */ 151 | #define ERROR_INVAL_SYMBOL 4 /* A symbol / expression was invalid. */ 152 | #define ERROR_MAX_SYMBOLS 5 /* Symbols or labels in the assembly source overflowed the symbol table. */ 153 | #define ERROR_TOO_LARGE 6 /* Assembly output produced was too large. */ 154 | #define ERROR_RANGE 7 /* Argument out of range. */ 155 | 156 | /** 157 | * Warning codes used in the editor and assembler. 158 | */ 159 | #define WARNING_UNSAVED 0 /* The file contains unsaved changes. */ 160 | #define WARNING_EXISTS 1 /* A file with the same name already exists. */ 161 | 162 | /** 163 | * Pointer to the start of editable data in the edit buffer. Note: Does not include file header. To include the file header, use EDIT_BUFFER - 2 164 | */ 165 | #define EDIT_BUFFER (uint8_t *)0xD52C02 166 | 167 | /** 168 | * Pointer to the start of the symbol table. 169 | */ 170 | #define SYMBOL_TABLE EDIT_BUFFER + MAX_FILE_SIZE + 128 171 | 172 | /** 173 | * Pointer to the start of where the assembler puts its output. (Start of VRAM) 174 | */ 175 | #define OUTPUT (uint8_t *)0xD40000 176 | 177 | /** 178 | * Maximum number of bytes for a token. (Highlighting) 179 | */ 180 | #define MAX_TOK_LENGTH_HL 6 181 | 182 | /** 183 | * Maximum number of bytes for a line. (Assembling) 184 | */ 185 | #define MAX_LINE_LENGTH_ASM 255 186 | 187 | /** 188 | * Maximum length of input string for the util_StringInputBox function. 189 | */ 190 | #define MAX_INPUT_LENGTH 9 191 | 192 | /** 193 | * Different modes for the ui_UpdateText function. 194 | */ 195 | #define UPDATE_ALL 0 /* Update all text currently on screen. */ 196 | #define UPDATE_TOP 1 /* Update only the top row of text on screen. */ 197 | #define UPDATE_BOTTOM 2 /* Update only the bottom row of text on screen. */ 198 | 199 | /** 200 | * Editor preferences struct, saved in the AppVar. 201 | */ 202 | struct preferences_t { 203 | bool theme; /* Color theme used by the editor. */ 204 | bool highlighting; /* Whether syntax highlighting is enabled. */ 205 | }; 206 | 207 | /** 208 | * Editor context struct. 209 | */ 210 | struct context_t { 211 | bool fileIsOpen; /* Whether a file is currently open in the editor. */ 212 | bool fileIsSaved; /* Whether the currently open file has been modified since it was last saved. */ 213 | char *pageDataStart; /* Start of data for the current page of the open file being displayed. */ 214 | char *rowDataStart; /* Start of data for the currently selected row in the editor. */ 215 | char fileName[9]; /* Name of currently opened file. */ 216 | uint16_t fileSize; /* Size of currently opened file (includes both data and header). */ 217 | char *openEOF; /* Pointer to the end of the currently opened file's data in the edit buffer. Because it points to the last byte of data, it is equal to EDIT_BUFFER - 1 in an empty file. */ 218 | unsigned int newlineCount; /* Total number of newline characters in the currently opened file's data. */ 219 | unsigned int totalLines; /* Total number of lines, after calculating word-wrap. */ 220 | unsigned int newlineStart; /* First line of the page, before word-wrap (using newline characters). */ 221 | unsigned int lineStart; /* First line of the page, after calculating word-wrap. */ 222 | unsigned int row; /* Current row of the editor selected by the cursor (0 - 13). */ 223 | uint8_t column; /* Current column of the editor selected by the cursor. */ 224 | uint8_t rowLength; /* Length of the currently selected row, in columns. */ 225 | uint8_t inputMode; /* Current text input mode (A, a, 1). */ 226 | }; 227 | 228 | /** 229 | * Opcode struct which holds the size and bytes associated with an opcode. 230 | */ 231 | struct opcode_t { 232 | uint8_t size; 233 | void *data; 234 | }; 235 | 236 | struct error_t { 237 | uint16_t line; 238 | uint8_t code; 239 | }; 240 | 241 | #ifdef __cplusplus 242 | } 243 | #endif 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /src/asm/files.asm: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - files.asm 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Copyright 2022 - 2025 6 | ; License: GPL-3.0 7 | ; 8 | ;-------------------------------------- 9 | 10 | assume adl=1 11 | 12 | section .text 13 | 14 | include 'include/equates.inc' 15 | 16 | public _asm_files_ReadFile 17 | public _asm_files_WriteFile 18 | public _asm_files_CheckFileExists 19 | public _asm_files_CountLines 20 | public _asm_files_GetLineLength 21 | public _asm_files_GetStartSpacesNumber 22 | public _asm_files_NextLine 23 | public _asm_files_PreviousLine 24 | public _asm_files_InsertChar 25 | public _asm_files_DeleteChar 26 | public _asm_files_CreateProg 27 | 28 | extern _asm_misc_ClearBuffer 29 | 30 | _asm_files_ReadFile: 31 | pop de 32 | ex (sp), hl 33 | push de 34 | ex de, hl 35 | ld hl, ti.OP1 36 | ld (hl), ti.AppVarObj 37 | inc hl 38 | ld bc, 8 39 | ex de, hl 40 | ldir ; move name to OP1 41 | call ti.ChkFindSym 42 | res 0, a 43 | ret c 44 | push de 45 | push bc 46 | ld hl, editBuffer - 2 47 | call _asm_misc_ClearBuffer + 3 48 | pop bc 49 | pop de 50 | call ti.ChkInRam 51 | ex de, hl 52 | jr z, .inRam 53 | ld de, 10 54 | add hl, de 55 | ld a, c 56 | ld bc, 0 57 | ld c, a 58 | add hl, bc 59 | 60 | .inRam: 61 | ld bc, 0 62 | ld c, (hl) 63 | inc hl 64 | ld b, (hl) ; get file size 65 | inc hl 66 | ld de, editBuffer - 2 67 | 68 | .loop: 69 | ld a, b 70 | or a, c 71 | jr z, .return 72 | ldir 73 | 74 | .return: 75 | ld a, 1 76 | ret 77 | 78 | _asm_files_WriteFile: 79 | ld iy, 0 80 | add iy, sp 81 | ld de, (iy + 6) 82 | ld hl, 128 ; be safe 83 | add hl, de 84 | call ti.EnoughMem 85 | jr nc, .continue 86 | xor a, a 87 | ret 88 | 89 | .continue: 90 | pop de 91 | ex (sp), hl 92 | push de 93 | ex de, hl 94 | push de 95 | ld iy, ti.flags 96 | ld hl, ti.OP1 97 | ld (hl), ti.AppVarObj 98 | inc hl 99 | ld bc, 8 100 | ex de, hl 101 | ldir ; move name to OP1 102 | call ti.ChkFindSym 103 | jr c, .createFile 104 | call ti.DelVarArc 105 | 106 | .createFile: 107 | pop de 108 | ld hl, ti.OP1 109 | ld (hl), ti.AppVarObj 110 | inc hl 111 | ld bc, 8 112 | ex de, hl 113 | ldir ; move name to OP1 114 | ld iy, 0 115 | add iy, sp 116 | ld hl, (iy + 6) 117 | call ti.CreateAppVar 118 | inc de 119 | inc de 120 | ld hl, editBuffer - 2 121 | ldir 122 | call ti.OP4ToOP1 123 | ld iy, ti.flags 124 | call ti.Arc_Unarc 125 | ld a, 1 126 | ret 127 | 128 | _asm_files_CheckFileExists: 129 | ld iy, 0 130 | add iy, sp 131 | ld de, (iy + 3) ; name of file 132 | ld a, (iy + 6) ; type of file 133 | ld hl, ti.OP1 134 | ld (hl), a 135 | inc hl 136 | ld bc, 8 137 | ex de, hl 138 | ldir ; move name to OP1 139 | call ti.ChkFindSym 140 | ld a, 0 141 | ret c 142 | inc a 143 | ret 144 | 145 | _asm_files_CountLines: 146 | ld iy, 0 147 | add iy, sp 148 | ld hl, (iy + 9) ; end of file 149 | ld (EOF), hl 150 | ld hl, editBuffer ; data start 151 | push iy ; save this for later 152 | ld de, 1 ; line count 153 | ld bc, 1 ; newline count 154 | ld iyl, c ; character count 155 | 156 | .loop: 157 | push bc 158 | push hl 159 | push hl 160 | pop bc 161 | call _checkEOF 162 | pop hl 163 | pop bc 164 | jr z, .return 165 | ld a, $0A 166 | cp a, (hl) 167 | jr z, .hitNewline 168 | ld a, 38 169 | cp a, iyl 170 | jr z, .hitMaxChars 171 | inc iyl 172 | inc hl 173 | jr .loop 174 | 175 | .hitMaxChars: 176 | inc hl 177 | ld a, $0A 178 | cp a, (hl) 179 | jr z, .hitNewline 180 | ld iyl, 1 181 | inc de 182 | jr .loop 183 | 184 | .hitNewline: 185 | inc bc 186 | inc de 187 | ld iyl, 1 188 | inc hl 189 | jr .loop 190 | 191 | .return: 192 | pop iy 193 | ld hl, (iy + 3) 194 | ld (hl), bc 195 | ld hl, (iy + 6) 196 | ld (hl), de 197 | ret 198 | 199 | _asm_files_GetLineLength: 200 | ld iy, 0 201 | add iy, sp 202 | ld hl, (iy + 6) ; EOF 203 | ld (EOF), hl 204 | ld hl, (iy + 3) ; current address 205 | ld bc, 0 206 | ld b, 38 ; max chars per line 207 | 208 | .loop: 209 | ld a, $0A ; newline 210 | cp a, (hl) 211 | jr z, .return 212 | 213 | .loopNext: 214 | push bc 215 | push hl 216 | pop bc 217 | call _checkEOF 218 | push bc 219 | pop hl 220 | pop bc 221 | jr z, .return 222 | inc c 223 | inc hl 224 | djnz .loop 225 | 226 | .return: 227 | ld a, c 228 | ret 229 | 230 | _asm_files_GetStartSpacesNumber: 231 | ld iy, 0 232 | add iy, sp 233 | ld hl, (iy + 6) ; EOF 234 | ld (EOF), hl 235 | ld hl, (iy + 3) ; line start address 236 | ld bc, 0 ; c = counter 237 | ld b, 38 ; max chars per line 238 | 239 | .loop: 240 | ld a, $20 241 | cp a, (hl) 242 | jr nz, .return 243 | 244 | .loopNext: 245 | push bc 246 | push hl 247 | pop bc 248 | call _checkEOF 249 | push bc 250 | pop hl 251 | pop bc 252 | jr z, .return ; return if EOF 253 | inc c 254 | inc hl 255 | djnz .loop ; decrease max chars and loop 256 | 257 | .return: 258 | ld a, c 259 | ret 260 | 261 | _asm_files_NextLine: 262 | ld iy, 0 263 | add iy, sp 264 | ld hl, (iy + 3) ; current address 265 | ld b, 38 ; max chars per line 266 | 267 | .loop: 268 | ld a, $0A 269 | cp a, (hl) 270 | jr z, .return 271 | inc hl 272 | djnz .loop 273 | cp a, (hl) ; skip extra newline if line was exactly the max length 274 | jr z, .return 275 | dec hl 276 | 277 | .return: 278 | inc hl 279 | ret 280 | 281 | _asm_files_PreviousLine: 282 | ld iy, 0 283 | add iy, sp 284 | ld hl, (iy + 6) ; end of file 285 | ld (EOF), hl 286 | ld hl, (iy + 3) ; current address 287 | ld (oldLineStart), hl 288 | dec hl 289 | ld a, $0A 290 | cp a, (hl) 291 | jr nz, .loopFindStart 292 | dec hl 293 | 294 | .loopFindStart: 295 | ld a, $0A ; newline 296 | cp a, (hl) 297 | jr z, .next 298 | ld a, $7A ; start of file header 299 | cp a, (hl) 300 | call z, .checkFileStart 301 | jr z, .next 302 | dec hl 303 | jr .loopFindStart 304 | 305 | .next: 306 | inc hl ; skip newline 307 | ld b, 38 308 | ld (currentLineStart), hl 309 | 310 | .loop: 311 | ld a, $0A 312 | cp a, (hl) 313 | jr z, .return 314 | push hl 315 | push bc 316 | push hl 317 | pop bc 318 | call _checkEOF 319 | pop bc 320 | pop hl 321 | jr z, .return 322 | inc hl 323 | djnz .loop 324 | cp a, (hl) ; skip extra newline if line was exactly the max length 325 | jr z, .loopNext 326 | dec hl 327 | 328 | .loopNext: 329 | inc hl 330 | push hl 331 | ex de, hl 332 | ld hl, (oldLineStart) 333 | xor a, a 334 | sbc hl, de 335 | pop hl 336 | jr z, .return 337 | ld (currentLineStart), hl 338 | ld b, 38 339 | jr .loop 340 | 341 | .return: 342 | ld hl, (currentLineStart) 343 | ret 344 | 345 | .checkFileStart: 346 | ex de, hl 347 | ld hl, editBuffer - 2 + 2 348 | dec hl 349 | xor a, a 350 | sbc hl, de 351 | ex de, hl 352 | ret 353 | 354 | _asm_files_InsertChar: 355 | ld iy, 0 356 | add iy, sp 357 | ld hl, (iy + 6) ; end of file 358 | ld bc, (iy + 9) ; size to copy 359 | push hl 360 | pop de 361 | inc de 362 | 363 | .loop: 364 | ld a, b 365 | or a, c 366 | jr z, .return 367 | ldd 368 | jr .loop 369 | 370 | .return: 371 | ld a, (iy + 3) ; character 372 | inc hl 373 | ld (hl), a 374 | ret 375 | 376 | _asm_files_DeleteChar: 377 | ld iy, 0 378 | add iy, sp 379 | ld hl, (iy + 3) ; what to start deleting 380 | ld bc, (iy + 6) ; size to copy 381 | push hl 382 | pop de 383 | inc hl 384 | 385 | .loop: 386 | ld a, b 387 | or a, c 388 | jr z, .return 389 | ldi 390 | jr .loop 391 | 392 | .return: 393 | dec hl 394 | ld (hl), 0 395 | ret 396 | 397 | _asm_files_CreateProg: 398 | pop de 399 | pop bc 400 | ex (sp), hl 401 | push bc 402 | push de 403 | call ti.SetBCUTo0 404 | push bc 405 | dec hl 406 | call ti.Mov9ToOP1 407 | ld a, ti.ProtProgObj 408 | ld (ti.OP1), a 409 | pop bc 410 | push bc 411 | ld hl, 128 412 | add hl, bc 413 | call ti.EnoughMem 414 | ld a, 2 415 | pop bc 416 | ret c 417 | push bc 418 | call ti.ChkFindSym 419 | call nc, ti.DelVarArc 420 | pop hl 421 | push hl 422 | call ti.CreateProtProg 423 | pop bc 424 | ld hl, ti.vRam 425 | inc de 426 | inc de 427 | ldir 428 | xor a, a 429 | ret 430 | 431 | _checkEOF: ; bc = current address being read; destroys hl and a 432 | ld hl, (EOF) 433 | inc hl 434 | or a, a 435 | sbc hl, bc 436 | ret nc 437 | cp a, a 438 | ret 439 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - parser.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "assembler.h" 13 | #include "highlight.h" 14 | #include "parser.h" 15 | #include "utility.h" 16 | #include "asm/misc.h" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | /** 23 | * Token currently being parsed. 24 | */ 25 | static char *nextToken; 26 | 27 | /** 28 | * Final byte of input. 29 | */ 30 | static char *inputEnd; 31 | 32 | /** 33 | * Error code returned by the parser if necessary. 34 | */ 35 | uint8_t *retErr; 36 | 37 | static char *parser_GetTokStart(char *token) { 38 | bool singleChar = true; 39 | 40 | while (*token != '\0') { 41 | if (*token == '(' || 42 | *token == ')' || 43 | *token == '+' || 44 | *token == '-' || 45 | *token == '*' || 46 | *token == '/' || 47 | *token == '>' || 48 | *token == '<' || 49 | *token == '&' || 50 | *token == '^' || 51 | *token == '|') { 52 | 53 | if (singleChar && (!strncmp(token - 1, "<<", 2) || !strncmp(token - 1, ">>", 2))) { 54 | return token - 1; 55 | } 56 | 57 | if (!(*token == '-' && ( 58 | *(token - 1) == '+' || 59 | *(token - 1) == '-' || 60 | *(token - 1) == '*' || 61 | *(token - 1) == '/' || 62 | *(token - 1) == '>' || 63 | *(token - 1) == '<' || 64 | *(token - 1) == '&' || 65 | *(token - 1) == '^' || 66 | *(token - 1) == '|' || 67 | *(token - 1) == '\0'))) { 68 | 69 | return token + !singleChar; 70 | } 71 | } 72 | 73 | singleChar = false; 74 | token--; 75 | } 76 | 77 | return token + 1; 78 | } 79 | 80 | /** 81 | * Parse bitwise OR 82 | * BR -> BR | BX 83 | * BR -> BX 84 | */ 85 | long parser_BR(void) { 86 | long BX = parser_BX(); 87 | 88 | if (*(nextToken - 1) == '\0') { 89 | return BX; 90 | } 91 | 92 | if (*nextToken == '|') { 93 | nextToken = parser_GetTokStart(nextToken - 1); 94 | 95 | dbg_printf(" | "); 96 | return parser_BR() | BX; 97 | } else { 98 | return BX; 99 | } 100 | } 101 | 102 | /** 103 | * Parse bitwise XOR 104 | * BX -> BX ^ BA 105 | * BX -> BA 106 | */ 107 | long parser_BX(void) { 108 | long BA = parser_BA(); 109 | 110 | if (*(nextToken - 1) == '\0') { 111 | return BA; 112 | } 113 | 114 | if (*nextToken == '^') { 115 | nextToken = parser_GetTokStart(nextToken - 1); 116 | 117 | dbg_printf(" ^ "); 118 | return parser_BX() ^ BA; 119 | } else { 120 | return BA; 121 | } 122 | } 123 | 124 | /** 125 | * Parse bitwise AND 126 | * BA -> BA & S 127 | * BA -> S 128 | */ 129 | long parser_BA(void) { 130 | long S = parser_S(); 131 | 132 | if (*(nextToken - 1) == '\0') { 133 | return S; 134 | } 135 | 136 | if (*nextToken == '&') { 137 | nextToken = parser_GetTokStart(nextToken - 1); 138 | 139 | dbg_printf(" & "); 140 | return parser_BA() & S; 141 | } else { 142 | return S; 143 | } 144 | } 145 | 146 | /** 147 | * Parse left / right bitshift 148 | * S -> S << E 149 | * S -> S >> E 150 | * S -> E 151 | */ 152 | long parser_S(void) { 153 | long E = parser_E(); 154 | 155 | if (*(nextToken - 1) == '\0') { 156 | return E; 157 | } 158 | 159 | if (!strncmp(nextToken, "<<", 2)) { 160 | nextToken = parser_GetTokStart(nextToken - 1); 161 | 162 | dbg_printf(" << "); 163 | return parser_S() << E; 164 | } else if (!strncmp(nextToken, ">>", 2)) { 165 | nextToken = parser_GetTokStart(nextToken - 1); 166 | 167 | dbg_printf(" >> "); 168 | return parser_S() >> E; 169 | } else { 170 | return E; 171 | } 172 | } 173 | 174 | /** 175 | * Parse expression 176 | * E -> E + T 177 | * E -> E - T 178 | * E -> T 179 | */ 180 | long parser_E(void) { 181 | long T = parser_T(); 182 | 183 | if (*(nextToken - 1) == '\0') { 184 | return T; 185 | } 186 | 187 | if (*nextToken == '+') { 188 | nextToken = parser_GetTokStart(nextToken - 1); 189 | 190 | dbg_printf(" + "); 191 | return parser_E() + T; 192 | } else if (*nextToken == '-') { 193 | nextToken = parser_GetTokStart(nextToken - 1); 194 | 195 | dbg_printf(" - "); 196 | return parser_E() - T; 197 | } else { 198 | return T; 199 | } 200 | } 201 | 202 | /** 203 | * Parse term 204 | * T -> T * F 205 | * T -> T / F 206 | * T -> F 207 | */ 208 | long parser_T(void) { 209 | long F = parser_F(); 210 | 211 | if (*(nextToken - 1) == '\0') { 212 | return F; 213 | } 214 | 215 | if (*nextToken == '*') { 216 | nextToken = parser_GetTokStart(nextToken - 1); 217 | 218 | dbg_printf(" * "); 219 | return parser_T() * F; 220 | } else if (*nextToken == '/') { 221 | nextToken = parser_GetTokStart(nextToken - 1); 222 | 223 | dbg_printf(" / "); 224 | return parser_T() / F; 225 | } else { 226 | return F; 227 | } 228 | } 229 | 230 | /** 231 | * Parse factor 232 | * F -> Symbol 233 | * F -> Int 234 | * F -> ( BR ) 235 | * F -> -F 236 | */ 237 | long parser_F(void) { 238 | char *parseNum; 239 | parseNum = nextToken; 240 | nextToken = parser_GetTokStart(nextToken - 1); 241 | 242 | if (hlight_Number(parseNum, util_GetStringEnd(parseNum, inputEnd, true))) { 243 | dbg_printf("%p | %s\n", parseNum, parseNum); 244 | uint8_t base = NUMBER_DEC; 245 | 246 | if (*parseNum == '$') { 247 | base = NUMBER_HEX; 248 | } else if (*parseNum == '0' && *(parseNum + 1) == 'x') { 249 | base = NUMBER_HEX; 250 | parseNum++; 251 | } else if (*parseNum == '@') { 252 | base = NUMBER_OCT; 253 | } else if (*parseNum == '%') { 254 | base = NUMBER_BIN; 255 | } else { 256 | parseNum--; 257 | } 258 | 259 | parseNum++; 260 | 261 | dbg_printf("%ld", strtol(parseNum, NULL, base)); 262 | 263 | return strtol(parseNum, NULL, base); 264 | } else if (assembler_IsChar(parseNum)) { 265 | dbg_printf("%d", *(parseNum + 1 + (*(parseNum + 1) == '\\'))); 266 | return *(parseNum + 1 + (*(parseNum + 1) == '\\')); 267 | } else if (*parseNum == ')') { 268 | dbg_printf("("); 269 | long BR = parser_BR(); 270 | 271 | if (*nextToken == '(') { 272 | dbg_printf(")"); 273 | nextToken = parser_GetTokStart(nextToken - 1); 274 | return BR; 275 | } else { 276 | *retErr = ERROR_INVAL_SYMBOL; 277 | return 0; 278 | } 279 | } else if (*parseNum == '-') { 280 | dbg_printf("-"); 281 | nextToken = parseNum; 282 | nextToken++; 283 | long F = parser_F(); 284 | nextToken = parser_GetTokStart(parseNum - 1); 285 | return -F; 286 | } 287 | 288 | static char symbol[MAX_LINE_LENGTH_ASM]; 289 | strncpy(symbol, parseNum, util_GetStringEnd(parseNum, inputEnd, true) - parseNum); 290 | symbol[util_GetStringEnd(parseNum, inputEnd, true) - parseNum] = '\0'; 291 | 292 | unsigned int includeCount; 293 | char *table = (char *)SYMBOL_TABLE; 294 | 295 | util_GetFiles(&includeCount, INCLUDE_HEADER); 296 | 297 | for (; (int)includeCount >= 0; includeCount--) { 298 | void *found = asm_misc_FindSymbol(symbol, table); 299 | 300 | if (found != NULL) { 301 | found += strlen(found) + 1; 302 | 303 | memset(symbol, '\0', sizeof(char) * sizeof(long)); 304 | memcpy(symbol, found + 1, *(uint8_t *)found); 305 | dbg_printf("%ld", *(long *)symbol); 306 | return *(long *)symbol; 307 | } 308 | 309 | if (includeCount) { 310 | uint8_t slot = ti_Open((const char *)(os_PixelShadow + (includeCount - 1) * 9), "r"); 311 | table = ti_GetDataPtr(slot); 312 | ti_Close(slot); 313 | 314 | if ((unsigned)symbol[0] - 'A' < 26) { 315 | table += ((uint16_t *)(table + 8))[symbol[0] - 'A']; 316 | } else if ((unsigned)symbol[0] - 'a' < 26) { 317 | table += ((uint16_t *)(table + 8))[26 + symbol[0] - 'a']; 318 | } else if (symbol[0] == '_') { 319 | table += ((uint16_t *)(table + 8))[52]; 320 | } else { 321 | table += ((uint16_t *)(table + 8))[53]; 322 | } 323 | } 324 | } 325 | 326 | *retErr = ERROR_INVAL_SYMBOL; 327 | return 0; 328 | } 329 | 330 | unsigned long parser_Eval(char *input, uint8_t *error) { 331 | unsigned int length = strlen(input); 332 | retErr = error; 333 | asm_misc_ReverseCopy(input + length - 1, input + length, length); // Very cursed parsing reversed 334 | *input = '\0'; 335 | inputEnd = input + length; 336 | nextToken = parser_GetTokStart(input + length); 337 | return parser_BR(); 338 | } 339 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - ui.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "defines.h" 13 | #include "utility.h" 14 | #include "highlight.h" 15 | #include "asm/files.h" 16 | #include "asm/spi.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | // Draw scrollbar 24 | void ui_DrawScrollbar(unsigned int x, uint8_t y, uint8_t boxHeight, unsigned int totalLines, unsigned int startLine, uint8_t linesPerPage) { 25 | if (totalLines) { 26 | unsigned int scrollBarLength = (float)boxHeight / ((float)totalLines / (float)linesPerPage) + 1; 27 | 28 | if (scrollBarLength > boxHeight) { 29 | scrollBarLength = boxHeight; 30 | } 31 | 32 | uint8_t scrollOffset = (float)boxHeight / (float)totalLines * (float)startLine; 33 | 34 | gfx_SetColor(BACKGROUND); 35 | gfx_FillRectangle_NoClip(x, y, 8, boxHeight); 36 | gfx_SetColor(CURSOR); 37 | gfx_FillRectangle_NoClip(x, y + scrollOffset, 8, scrollBarLength); 38 | } 39 | } 40 | 41 | // Draw the main UI elements 42 | void ui_DrawUIMain(uint8_t button, unsigned int totalLines, unsigned int startLine) { 43 | // Clear area behind buttons and scrollbar 44 | gfx_SetColor(BACKGROUND); 45 | gfx_FillRectangle_NoClip(0, 225, 320, 15); 46 | gfx_FillRectangle_NoClip(312, 14, 8, 209); 47 | 48 | // If a menu button is pressed, highlight it 49 | if (button) { 50 | gfx_SetColor(CURSOR); 51 | gfx_FillRectangle_NoClip(0 + (button - 1) * 65 - (button - 2), 225, 63, 15); 52 | } 53 | 54 | // Draw the borders around the buttons and scrollbar 55 | gfx_SetColor(OUTLINE); 56 | gfx_Rectangle_NoClip(0, 223, 320, 2); 57 | gfx_Rectangle_NoClip(310, 0, 2, 223); 58 | gfx_Rectangle_NoClip(63, 225, 2, 15); 59 | gfx_Rectangle_NoClip(127, 225, 2, 15); 60 | gfx_Rectangle_NoClip(191, 225, 2, 15); 61 | gfx_Rectangle_NoClip(255, 225, 2, 15); 62 | 63 | // Button text 64 | fontlib_SetForegroundColor(TEXT_DEFAULT); 65 | fontlib_SetCursorPosition(18, 227); 66 | fontlib_DrawString("File"); 67 | fontlib_SetCursorPosition(78, 227); 68 | fontlib_DrawString("Build"); 69 | fontlib_SetCursorPosition(146, 227); 70 | fontlib_DrawString("Goto"); 71 | fontlib_SetCursorPosition(206, 227); 72 | fontlib_DrawString("Chars"); 73 | fontlib_SetCursorPosition(260, 227); 74 | fontlib_DrawString("Settings"); 75 | 76 | ui_DrawScrollbar(312, 14, 209, totalLines, startLine, 14); 77 | } 78 | 79 | // Draw a box with the menu items/cursor 80 | void ui_DrawMenuBox(unsigned int x, uint8_t y, uint8_t width, uint8_t height, uint8_t option, unsigned int optionCount, ...) { 81 | gfx_SetColor(BACKGROUND); 82 | gfx_FillRectangle_NoClip(x, y, width, height); 83 | 84 | gfx_SetColor(OUTLINE); 85 | gfx_FillRectangle_NoClip(x + 1 + (2 * (x > 0)), y + 3 + (option * 17), width - 4 - (2 * ((x > 0) && (x + width < 310))), 17); 86 | 87 | // Menu outline 88 | gfx_FillRectangle_NoClip(x, y, width, 2); 89 | 90 | if (x > 0) { 91 | gfx_FillRectangle_NoClip(x, y + 2, 2, height - 2); 92 | } 93 | 94 | if (x + width < 310) { 95 | gfx_FillRectangle_NoClip(x + width - 2, y + 2, 2, height - 2); 96 | } 97 | 98 | if (y + height < 223) { 99 | gfx_FillRectangle_NoClip(x, y + height - 2, width, 2); 100 | } 101 | 102 | va_list menuNames; 103 | va_start(menuNames, optionCount); 104 | 105 | fontlib_SetForegroundColor(TEXT_DEFAULT); 106 | 107 | for (unsigned int drawYOffset = 6; drawYOffset < 6 + optionCount * 17; drawYOffset += 17) { 108 | fontlib_SetCursorPosition(x + 4 + (2 * (x > 0)), y + drawYOffset); 109 | fontlib_DrawString(va_arg(menuNames, char *)); 110 | } 111 | 112 | va_end(menuNames); 113 | } 114 | 115 | // Do this when no file is open 116 | void ui_NoFile(void) { 117 | gfx_ZeroScreen(); 118 | ui_DrawUIMain(0, 0, 0); 119 | fontlib_SetCursorPosition(81, 98); 120 | fontlib_DrawString("Open or create a file"); 121 | fontlib_SetCursorPosition(103, 110); 122 | fontlib_DrawString("to get started."); 123 | } 124 | 125 | // Check if top line was a highlightComment, string, etc. 126 | static bool ui_CheckIsComment(char *dataStart) { 127 | while (dataStart >= (char *)EDIT_BUFFER && *dataStart != '\n') { 128 | if (*dataStart == ';') { 129 | return true; // No need to keep searching 130 | } 131 | 132 | dataStart--; 133 | } 134 | 135 | return false; 136 | } 137 | 138 | static bool ui_CheckIsString(char *dataStart) { 139 | bool isString = false; 140 | 141 | if ((*dataStart == '\"' || *dataStart == '\'') && (*(dataStart - 1) != '\\')) { 142 | return true; 143 | } 144 | 145 | while (dataStart >= (char *)EDIT_BUFFER && *dataStart != '\n') { 146 | if ((*dataStart == '\"' || *dataStart == '\'') && (*(dataStart - 1) != '\\')) { 147 | isString = !isString; 148 | } 149 | 150 | dataStart--; 151 | } 152 | 153 | return isString; 154 | } 155 | 156 | // Print a line 157 | static char *ui_PrintLine(struct context_t *studioContext, char *string, bool highlighting, uint8_t row, unsigned int line) { 158 | bool highlightComment = false; 159 | bool highlightString = false; 160 | bool highlightInstruction = false; 161 | char *stringEnd = util_GetStringEnd(string, studioContext->openEOF, false); 162 | 163 | fontlib_SetCursorPosition(0, 2 + row * 16); 164 | 165 | if ((*(string - 1) == '\n') || (!(studioContext->lineStart) && !row)) { 166 | fontlib_SetForegroundColor(TEXT_DEFAULT); 167 | fontlib_DrawInt(line, 4); 168 | fontlib_DrawString(": "); 169 | } else { 170 | fontlib_ShiftCursorPosition(42, 0); 171 | } 172 | 173 | if (highlighting) { // correctly highlight first item in case it was wrapped from a previous line 174 | char *tempString = util_GetStringStart(string); 175 | 176 | if (ui_CheckIsComment(tempString)) { 177 | fontlib_SetForegroundColor(TEXT_COMMENT); 178 | highlightComment = true; 179 | } else if (ui_CheckIsString(tempString)) { 180 | fontlib_SetForegroundColor(TEXT_STRING); 181 | highlightString = true; 182 | } else { 183 | fontlib_SetForegroundColor(hlight_GetHighlightColor(tempString, stringEnd, highlighting)); 184 | } 185 | } 186 | 187 | uint8_t charsDrawn = 0; 188 | 189 | while (*string != '\n' && string <= studioContext->openEOF) { 190 | if (string >= stringEnd && highlighting) { 191 | if (fontlib_GetForegroundColor() == TEXT_INSTRUCTION) { 192 | highlightInstruction = true; 193 | } 194 | 195 | if (!highlightString && *string == ';') { 196 | fontlib_SetForegroundColor(TEXT_COMMENT); 197 | highlightComment = true; 198 | } 199 | 200 | if (!highlightComment) { 201 | if (*string == '\"' || *string == '\'' || *(string - 1) == '\"' || *(string - 1) == '\'') { 202 | if (ui_CheckIsString(string)) { 203 | fontlib_SetForegroundColor(TEXT_STRING); 204 | highlightString = true; 205 | } else if (highlightString) { 206 | stringEnd = util_GetStringEnd(string, studioContext->openEOF, false); 207 | fontlib_SetForegroundColor(hlight_GetHighlightColor(string, stringEnd, highlighting)); 208 | highlightString = false; 209 | } 210 | } 211 | } 212 | 213 | if (!highlightComment && !highlightString) { 214 | stringEnd = util_GetStringEnd(string, studioContext->openEOF, false); 215 | fontlib_SetForegroundColor(hlight_GetHighlightColor(string, stringEnd, highlighting)); 216 | } 217 | 218 | if (fontlib_GetForegroundColor() == TEXT_DEFAULT && highlightInstruction && ((unsigned)(*string - '0') < 10 || (unsigned)(*string - 'A') < 26 || (unsigned)(*string - 'a') < 26 || *string == '.' || *string == '_')) { 219 | fontlib_SetForegroundColor(TEXT_LABEL); 220 | } 221 | 222 | } 223 | 224 | fontlib_DrawGlyph(*string); 225 | 226 | charsDrawn++; 227 | string++; 228 | 229 | if (charsDrawn == 38 && *string != '\n') { // Loop to next line if the text continues past the edge 230 | return string; 231 | } 232 | } 233 | 234 | string++; // Skip newline 235 | return string; 236 | } 237 | 238 | void ui_DrawCursor(uint8_t row, uint8_t column, bool cursorActive, bool erase) { 239 | asm_spi_BeginFrame(); 240 | 241 | if (erase) { 242 | gfx_SetColor(BACKGROUND); 243 | } else { 244 | gfx_SetColor(CURSOR); 245 | } 246 | 247 | gfx_FillRectangle_NoClip(0, 1 + row * 16, 310, 1); // Highlight currently selected row 248 | gfx_FillRectangle_NoClip(0, 14 + row * 16, 310, 1); 249 | 250 | if (!cursorActive) { 251 | gfx_SetColor(BACKGROUND); 252 | } 253 | 254 | gfx_FillRectangle_NoClip(41 + column * 7, 3 + row * 16, 2, 10); // Cursor 255 | } 256 | 257 | void ui_UpdateText(struct context_t *studioContext, struct preferences_t *studioPreferences, uint8_t drawMode) { 258 | bool highlighting = studioPreferences->highlighting; 259 | gfx_SetColor(BACKGROUND); 260 | 261 | switch (drawMode) { 262 | case UPDATE_ALL: 263 | gfx_FillRectangle_NoClip(0, 0, 310, 223); 264 | break; 265 | case UPDATE_TOP: 266 | gfx_FillRectangle_NoClip(0, 1, 310, 14); 267 | break; 268 | case UPDATE_BOTTOM: 269 | gfx_FillRectangle_NoClip(0, 209, 310, 14); 270 | break; 271 | default: 272 | break; 273 | } 274 | 275 | unsigned int currentLine = studioContext->newlineStart + 1; 276 | char *textStart = studioContext->pageDataStart; 277 | 278 | for (uint8_t row = 0; row < 14; row++) { 279 | if (row == 13 || drawMode != UPDATE_BOTTOM) { 280 | textStart = ui_PrintLine(studioContext, textStart, highlighting, row, currentLine); 281 | } else { 282 | textStart = asm_files_NextLine(textStart); 283 | } 284 | 285 | if (drawMode == UPDATE_TOP) { 286 | return; 287 | } 288 | 289 | currentLine += (*(textStart - 1) == '\n'); 290 | 291 | if (textStart > studioContext->openEOF + 1) { 292 | break; 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/asm/lexer.asm: -------------------------------------------------------------------------------- 1 | ;-------------------------------------- 2 | ; 3 | ; eZ80 Studio Source Code - lexer.asm 4 | ; By RoccoLox Programs and TIny_Hacker 5 | ; Copyright 2022 - 2025 6 | ; License: GPL-3.0 7 | ; 8 | ;-------------------------------------- 9 | 10 | assume adl=1 11 | 12 | section .text 13 | 14 | include 'include/equates.inc' 15 | 16 | public _asm_lexer_TokType 17 | 18 | _asm_lexer_TokType: 19 | pop bc 20 | pop de ; Start of string 21 | ex (sp), hl ; End of string 22 | push de 23 | push bc 24 | xor a, a 25 | sbc hl, de 26 | ex de, hl 27 | ld a, (hl) 28 | dec e 29 | jp z, _lexer1 30 | cp a, 'a' 31 | jr nc, .lowerized 32 | add a, 'a' - 'A' 33 | 34 | .lowerized: 35 | ld (_smcFirstChar), a 36 | dec e 37 | jp z, _lexer2 38 | dec e 39 | jr z, _lexer3 40 | dec e 41 | jr z, _lexer4 42 | xor a, a 43 | dec e 44 | ret nz 45 | 46 | _lexer5: 47 | ld a, (hl) 48 | sub a, 'i' 49 | jp c, _noMatch 50 | cp a, 'u' + 1 - 'i' 51 | jp nc, _noMatch 52 | push hl 53 | ld hl, _lexer5AlphaLUT 54 | ld e, a 55 | add hl, de 56 | ld e, (hl) 57 | inc e 58 | jp z, _noMatchPop 59 | dec e 60 | ld hl, _lexer5LUT 61 | add hl, de 62 | ld a, 5 63 | ld (_smcLength), a 64 | inc a 65 | ld (_smcLengthPlus1), a 66 | jr _findMatch 67 | 68 | _lexer4: 69 | cp a, '.' + 'a' - 'A' 70 | jr nz, .notSuffix 71 | push hl 72 | ld hl, _lexer4Suffix 73 | jr .gotOffset 74 | 75 | .notSuffix: 76 | sub a, 'c' 77 | jp c, _noMatch 78 | cp a, 'r' + 1 - 'c' 79 | jr nc, _noMatch 80 | push hl 81 | ld hl, _lexer4AlphaLUT 82 | ld e, a 83 | add hl, de 84 | ld e, (hl) 85 | inc e 86 | jr z, _noMatchPop 87 | dec e 88 | ld hl, _lexer4LUT 89 | add hl, de 90 | 91 | .gotOffset: 92 | ld a, 4 93 | ld (_smcLength), a 94 | inc a 95 | ld (_smcLengthPlus1), a 96 | jr _findMatch 97 | 98 | _lexer3: 99 | sub a, 'a' 100 | jr c, _noMatch 101 | cp a, 'x' + 1 - 'a' 102 | jr nc, _noMatch 103 | push hl 104 | ld hl, _lexer3AlphaLUT 105 | ld e, a 106 | add hl, de 107 | ld e, (hl) 108 | inc e 109 | jr z, _noMatchPop 110 | dec e 111 | ld hl, _lexer3LUT 112 | add hl, de 113 | ld a, 3 114 | ld (_smcLength), a 115 | inc a 116 | ld (_smcLengthPlus1), a 117 | jr _findMatch 118 | 119 | _lexer2: 120 | sub a, 'a' 121 | jr c, _noMatch 122 | cp a, 's' + 1 - 'a' 123 | jr nc, _noMatch 124 | push hl 125 | ld hl, _lexer2AlphaLUT 126 | ld e, a 127 | add hl, de 128 | ld e, (hl) 129 | inc e 130 | jr z, _noMatchPop 131 | dec e 132 | ld hl, _lexer2LUT 133 | add hl, de 134 | ld a, 2 135 | ld (_smcLength), a 136 | inc a 137 | ld (_smcLengthPlus1), a 138 | 139 | _findMatch: 140 | ld b, 0 141 | _smcLength := $ - 1 142 | pop de 143 | push de 144 | push hl 145 | call _compare 146 | jr z, _found 147 | pop hl 148 | ld de, 0 149 | _smcLengthPlus1 := $ - 3 150 | add hl, de 151 | ld a, 0 152 | _smcFirstChar := $ - 1 153 | cp a, (hl) 154 | jr z, _findMatch 155 | 156 | _noMatchPop: 157 | pop hl 158 | 159 | _noMatch: 160 | xor a, a 161 | ret 162 | 163 | _found: 164 | ld a, (hl) 165 | pop de 166 | pop de 167 | ret 168 | 169 | _compare: 170 | ld a, (de) 171 | cp a, 'a' 172 | jr nc, .lowerized 173 | add a, 'a' - 'A' 174 | 175 | .lowerized: 176 | cp a, (hl) 177 | ret nz 178 | inc hl 179 | inc de 180 | djnz _compare 181 | ret 182 | 183 | _lexer1: 184 | cp a, 'a' 185 | jr nc, .lowerized 186 | add a, 'a' - 'A' 187 | 188 | .lowerized: 189 | sub a, 'a' 190 | jr c, _noMatch 191 | cp a, 'z' + 1 - 'a' 192 | jr nc, _noMatch ; also load correct return value 193 | ld hl, _lexer1LUT 194 | ld e, a 195 | add hl, de 196 | ld a, (hl) 197 | ret 198 | 199 | _lexer5AlphaLUT: 200 | db _lexer5I - _lexer5LUT ; 'i' 201 | db 255 ; 'j' 202 | db 255 ; 'k' 203 | db 255 ; 'l' 204 | db 255 ; 'm' 205 | db 255 ; 'n' 206 | db _lexer5O - _lexer5LUT ; 'o' 207 | db 255 ; 'p' 208 | db 255 ; 'q' 209 | db _lexer5R - _lexer5LUT ; 'r' 210 | db _lexer5S - _lexer5LUT ; 's' 211 | db _lexer5T - _lexer5LUT ; 't' 212 | 213 | _lexer5LUT: 214 | _lexer5I: 215 | db "ind2r", typeInstruction 216 | db "indmr", typeInstruction 217 | db "inirx", typeInstruction 218 | 219 | _lexer5O: 220 | db "otdmr", typeInstruction 221 | db "otdrx", typeInstruction 222 | db "otimr", typeInstruction 223 | db "otirx", typeInstruction 224 | db "outd2", typeInstruction 225 | db "otd2r", typeInstruction 226 | db "outi2", typeInstruction 227 | db "oti2r", typeInstruction 228 | 229 | _lexer5R: 230 | db "rsmix", typeInstruction 231 | 232 | _lexer5S: 233 | db "stmix", typeInstruction 234 | 235 | _lexer5T: 236 | db "tstio", typeInstruction 237 | 238 | _lexer4AlphaLUT: 239 | db _lexer4C - _lexer4LUT ; 'c' 240 | db _lexer4D - _lexer4LUT ; 'd' 241 | db 255 ; 'e' 242 | db 255 ; 'f' 243 | db 255 ; 'g' 244 | db _lexer4H - _lexer4LUT ; 'h' 245 | db _lexer4I - _lexer4LUT ; 'i' 246 | db 255 ; 'j' 247 | db 255 ; 'k' 248 | db _lexer4L - _lexer4LUT ; 'l' 249 | db 255 ; 'm' 250 | db 255 ; 'n' 251 | db _lexer4O - _lexer4LUT ; 'o' 252 | db _lexer4P - _lexer4LUT ; 'p' 253 | db 255 ; 'q' 254 | db _lexer4R - _lexer4LUT ; 'r' 255 | 256 | _lexer4LUT: 257 | _lexer4Suffix: 258 | db "Nsis", typeModifier ; N instead of . because it broke since we correct cases 259 | db "Nsil", typeModifier 260 | db "Nlis", typeModifier 261 | db "Nlil", typeModifier 262 | 263 | _lexer4C: 264 | db "call", typeInstruction 265 | db "cpir", typeInstruction 266 | db "cpdr", typeInstruction 267 | 268 | _lexer4D: 269 | db "djnz", typeInstruction 270 | 271 | _lexer4H: 272 | db "halt", typeInstruction 273 | 274 | _lexer4I: 275 | db "indr", typeInstruction 276 | db "ind2", typeInstruction 277 | db "indm", typeInstruction 278 | db "ini2", typeInstruction 279 | db "inim", typeInstruction 280 | 281 | _lexer4L: 282 | db "ldir", typeInstruction 283 | db "lddr", typeInstruction 284 | 285 | _lexer4O: 286 | db "otdm", typeInstruction 287 | db "otim", typeInstruction 288 | db "out0", typeInstruction 289 | db "outd", typeInstruction 290 | db "outi", typeInstruction 291 | 292 | _lexer4P: 293 | db "push", typeInstruction 294 | 295 | _lexer4R: 296 | db "reti", typeInstruction 297 | db "retn", typeInstruction 298 | db "rlca", typeInstruction 299 | db "rrca", typeInstruction 300 | 301 | _lexer3AlphaLUT: 302 | db _lexer3A - _lexer3LUT ; 'a' 303 | db _lexer3B - _lexer3LUT ; 'b' 304 | db _lexer3C - _lexer3LUT ; 'c' 305 | db _lexer3D - _lexer3LUT ; 'd' 306 | db _lexer3E - _lexer3LUT ; 'e' 307 | db 255 ; 'f' 308 | db 255 ; 'g' 309 | db 255 ; 'h' 310 | db _lexer3I - _lexer3LUT ; 'i' 311 | db 255 ; 'j' 312 | db 255 ; 'k' 313 | db _lexer3L - _lexer3LUT ; 'l' 314 | db _lexer3M - _lexer3LUT ; 'm' 315 | db _lexer3N - _lexer3LUT ; 'n' 316 | db _lexer3O - _lexer3LUT ; 'o' 317 | db _lexer3P - _lexer3LUT ; 'p' 318 | db 255 ; 'q' 319 | db _lexer3R - _lexer3LUT ; 'r' 320 | db _lexer3S - _lexer3LUT ; 's' 321 | db _lexer3T - _lexer3LUT ; 't' 322 | db 255 ; 'u' 323 | db 255 ; 'v' 324 | db 255 ; 'w' 325 | db _lexer3X - _lexer3LUT ; 'x' 326 | 327 | _lexer3LUT: 328 | _lexer3A: 329 | db "add", typeInstruction 330 | db "and", typeInstruction 331 | db "adc", typeInstruction 332 | db "af\'", typeRegister 333 | 334 | _lexer3B: 335 | db "bit", typeInstruction 336 | 337 | _lexer3C: 338 | db "cpi", typeInstruction 339 | db "cpd", typeInstruction 340 | db "ccf", typeInstruction 341 | db "cpl", typeInstruction 342 | 343 | _lexer3D: 344 | db "dec", typeInstruction 345 | db "daa", typeInstruction 346 | 347 | _lexer3E: 348 | db "exx", typeInstruction 349 | 350 | _lexer3I: 351 | db "inc", typeInstruction 352 | db "ixh", typeRegister 353 | db "ixl", typeRegister 354 | db "iyh", typeRegister 355 | db "iyl", typeRegister 356 | db "in0", typeInstruction 357 | db "ind", typeInstruction 358 | db "ini", typeInstruction 359 | 360 | _lexer3L: 361 | db "ldi", typeInstruction 362 | db "ldd", typeInstruction 363 | db "lea", typeInstruction 364 | 365 | _lexer3M: 366 | db "mlt", typeInstruction 367 | 368 | _lexer3N: 369 | db "neg", typeInstruction 370 | db "nop", typeInstruction 371 | 372 | _lexer3O: 373 | db "out", typeInstruction 374 | 375 | _lexer3P: 376 | db "pop", typeInstruction 377 | db "pea", typeInstruction 378 | 379 | _lexer3R: 380 | db "ret", typeInstruction 381 | db "res", typeInstruction 382 | db "rla", typeInstruction 383 | db "rlc", typeInstruction 384 | db "rld", typeInstruction 385 | db "rra", typeInstruction 386 | db "rrc", typeInstruction 387 | db "rrd", typeInstruction 388 | db "rst", typeInstruction 389 | 390 | _lexer3S: 391 | db "sbc", typeInstruction 392 | db "set", typeInstruction 393 | db "sub", typeInstruction 394 | db "sla", typeInstruction 395 | db "sll", typeInstruction 396 | db "sra", typeInstruction 397 | db "srl", typeInstruction 398 | db "scf", typeInstruction 399 | db "slp", typeInstruction 400 | 401 | _lexer3T: 402 | db "tst", typeInstruction 403 | 404 | _lexer3X: 405 | db "xor", typeInstruction 406 | 407 | _lexer2AlphaLUT: 408 | db _lexer2A - _lexer2LUT ; 'a' 409 | db _lexer2B - _lexer2LUT ; 'b' 410 | db _lexer2C - _lexer2LUT ; 'c' 411 | db _lexer2D - _lexer2LUT ; 'd' 412 | db _lexer2E - _lexer2LUT ; 'e' 413 | db 255 ; 'f' 414 | db 255 ; 'g' 415 | db _lexer2H - _lexer2LUT ; 'h' 416 | db _lexer2I - _lexer2LUT ; 'i' 417 | db _lexer2J - _lexer2LUT ; 'j' 418 | db 255 ; 'k' 419 | db _lexer2L - _lexer2LUT ; 'l' 420 | db _lexer2M - _lexer2LUT ; 'm' 421 | db _lexer2N - _lexer2LUT ; 'n' 422 | db _lexer2O - _lexer2LUT ; 'o' 423 | db _lexer2P - _lexer2LUT ; 'p' 424 | db 255 ; 'q' 425 | db _lexer2R - _lexer2LUT ; 'r' 426 | db _lexer2S - _lexer2LUT ; 's' 427 | 428 | _lexer2LUT: 429 | _lexer2A: 430 | db "af", typeRegister 431 | 432 | _lexer2B: 433 | db "bc", typeRegister 434 | 435 | _lexer2C: 436 | db "cp", typeInstruction 437 | 438 | _lexer2D: 439 | db "db", typeInstruction 440 | db "dw", typeInstruction 441 | db "dl", typeInstruction 442 | db "dd", typeInstruction 443 | db "de", typeRegister 444 | db "di", typeInstruction 445 | 446 | _lexer2E: 447 | db "ei", typeInstruction 448 | db "ex", typeInstruction 449 | 450 | _lexer2H: 451 | db "hl", typeRegister 452 | 453 | _lexer2I: 454 | db "ix", typeRegister 455 | db "iy", typeRegister 456 | db "in", typeInstruction 457 | db "im", typeInstruction 458 | 459 | _lexer2J: 460 | db "jp", typeInstruction 461 | db "jr", typeInstruction 462 | 463 | _lexer2L: 464 | db "ld", typeInstruction 465 | 466 | _lexer2M: 467 | db "mb", typeRegister 468 | 469 | _lexer2N: 470 | db "nz", typeCondition 471 | db "nc", typeCondition 472 | 473 | _lexer2O: 474 | db "or", typeInstruction 475 | 476 | _lexer2P: 477 | db "pe", typeCondition 478 | db "po", typeCondition 479 | 480 | _lexer2R: 481 | db "rb", typeInstruction 482 | db "rw", typeInstruction 483 | db "rl", typeInstruction 484 | db "rd", typeInstruction 485 | db "rr", typeInstruction 486 | 487 | _lexer2S: 488 | db "sp", typeRegister 489 | 490 | _lexer1LUT: 491 | db typeRegister ; 'a' 492 | db typeRegister ; 'b' 493 | db typeRegister ; 'c' 494 | db typeRegister ; 'd' 495 | db typeRegister ; 'e' 496 | db typeRegister ; 'f' 497 | db 0 ; 'g' 498 | db typeRegister ; 'h' 499 | db typeRegister ; 'i' 500 | db 0 ; 'j' 501 | db 0 ; 'k' 502 | db typeRegister ; 'l' 503 | db typeCondition ; 'm' 504 | db 0 ; 'n' 505 | db 0 ; 'o' 506 | db typeCondition ; 'p' 507 | db 0 ; 'q' 508 | db typeRegister ; 'r' 509 | db 0 ; 's' 510 | db 0 ; 't' 511 | db 0 ; 'u' 512 | db 0 ; 'v' 513 | db 0 ; 'w' 514 | db 0 ; 'x' 515 | db 0 ; 'y' 516 | db typeCondition ; 'z' 517 | -------------------------------------------------------------------------------- /src/utility.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - utility.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "utility.h" 13 | #include "defines.h" 14 | #include "asm/misc.h" 15 | #include "asm/files.h" 16 | #include "asm/spi.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static void util_SetDefaults(struct preferences_t *studioPreferences) { 24 | studioPreferences->theme = LIGHT_THEME; 25 | studioPreferences->highlighting = true; 26 | } 27 | 28 | void util_ReadPrefs(struct preferences_t *studioPreferences) { 29 | uint8_t appvar = ti_Open("EzStudio", "r"); 30 | 31 | if (appvar) { 32 | ti_Read(studioPreferences, sizeof(struct preferences_t), 1, appvar); 33 | } else { 34 | util_SetDefaults(studioPreferences); 35 | } 36 | 37 | ti_Close(appvar); 38 | } 39 | 40 | void util_WritePrefs(struct preferences_t *studioPreferences) { 41 | uint8_t appvar = ti_Open("EzStudio", "w"); 42 | 43 | ti_Write(studioPreferences, sizeof(struct preferences_t), 1, appvar); 44 | ti_SetArchiveStatus(true, appvar); 45 | } 46 | 47 | void util_GetFiles(unsigned int *fileCount, char *header) { 48 | uint8_t fileType = '\0'; 49 | char *fileName; 50 | void *vatPtr = NULL; 51 | *fileCount = 0; 52 | unsigned int currentOffset = 0; 53 | 54 | asm_misc_SortVAT(); 55 | 56 | while ((fileName = ti_DetectAny(&vatPtr, header, &fileType))) { 57 | if (fileType == OS_TYPE_APPVAR) { 58 | strcpy(&((char *)os_PixelShadow)[currentOffset], fileName); 59 | currentOffset += 9; 60 | *fileCount = *fileCount + 1; 61 | } 62 | } 63 | 64 | } 65 | 66 | char *util_GetStringEnd(char *string, char *openEOF, bool parser) { 67 | bool singleChar = true; 68 | 69 | if (!parser && *string == '.') { 70 | string++; 71 | } 72 | 73 | while (string <= openEOF) { 74 | if (*string == ' ' || 75 | *string == '\n' || 76 | *string == ';' || 77 | *string == '(' || 78 | *string == ')' || 79 | *string == ',' || 80 | *string == '=' || 81 | *string == '+' || 82 | *string == '-' || 83 | *string == '*' || 84 | *string == '/' || 85 | *string == '<' || 86 | *string == '>' || 87 | *string == '&' || 88 | *string == '^' || 89 | *string == '|' || 90 | (!parser && *string == '.')) { 91 | 92 | return string + singleChar; 93 | } 94 | 95 | singleChar = false; 96 | string++; 97 | } 98 | 99 | return string; 100 | } 101 | 102 | char *util_GetStringStart(char *string) { 103 | bool singleChar = true; 104 | 105 | while (string >= (char *)EDIT_BUFFER) { 106 | if (*string == ' ' || 107 | *string == '\n' || 108 | *string == ';' || 109 | *string == '(' || 110 | *string == ')' || 111 | *string == '.' || 112 | *string == ',' || 113 | *string == '=' || 114 | *string == '+' || 115 | *string == '-' || 116 | *string == '*' || 117 | *string == '/' || 118 | *string == '<' || 119 | *string == '>' || 120 | *string == '&' || 121 | *string == '^' || 122 | *string == '|') { 123 | 124 | return string + !singleChar; 125 | } 126 | 127 | singleChar = false; 128 | string--; 129 | } 130 | 131 | return string + !singleChar; 132 | } 133 | 134 | bool util_InsertChar(char character, struct context_t *studioContext) { 135 | if (character) { 136 | if (studioContext->fileIsSaved) { 137 | studioContext->fileIsSaved = false; 138 | } 139 | 140 | asm_files_InsertChar(character, studioContext->openEOF, studioContext->openEOF - (studioContext->rowDataStart + studioContext->column) + 1); 141 | 142 | studioContext->fileSize++; 143 | studioContext->openEOF++; 144 | 145 | asm_files_CountLines(&(studioContext->newlineCount), &(studioContext->totalLines), studioContext->openEOF); 146 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 147 | 148 | if (character == '\n') { 149 | // get the number of spaces at the start of the previous line 150 | uint8_t requiredSpacesNumber = asm_files_GetStartSpacesNumber(studioContext->rowDataStart, studioContext->openEOF); 151 | studioContext->column = 0; 152 | 153 | if (studioContext->row < 13 && studioContext->lineStart + studioContext->row + 1 != studioContext->totalLines) { 154 | studioContext->row++; 155 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 156 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 157 | } else { 158 | studioContext->lineStart++; 159 | studioContext->pageDataStart = asm_files_NextLine(studioContext->pageDataStart); 160 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 161 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 162 | studioContext->newlineStart += (*(studioContext->pageDataStart - 1) == '\n'); 163 | } 164 | 165 | // compute current number of spaces 166 | uint8_t currentSpacesNumber = asm_files_GetStartSpacesNumber(studioContext->rowDataStart, studioContext->openEOF); 167 | 168 | // insert required number of spaces 169 | if (currentSpacesNumber < requiredSpacesNumber) { 170 | for (uint8_t i = 0; i < requiredSpacesNumber - currentSpacesNumber; i++) { 171 | asm_files_InsertChar(' ', studioContext->openEOF, studioContext->openEOF - (studioContext->rowDataStart + studioContext->column) + 1); 172 | studioContext->column++; 173 | studioContext->fileSize++; 174 | studioContext->openEOF++; 175 | } 176 | } 177 | 178 | // update total row length 179 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 180 | } else { 181 | if (studioContext->column < studioContext->rowLength) { 182 | studioContext->column++; 183 | } else { 184 | if (studioContext->row < 13 && studioContext->lineStart + studioContext->row + 1 != studioContext->totalLines) { 185 | studioContext->row++; 186 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 187 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 188 | studioContext->column = 1; // Skip the new character 189 | } else if (studioContext->lineStart + 14 < studioContext->totalLines) { 190 | studioContext->lineStart++; 191 | studioContext->pageDataStart = asm_files_NextLine(studioContext->pageDataStart); 192 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 193 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 194 | studioContext->column = 1; 195 | studioContext->newlineStart += (*(studioContext->pageDataStart - 1) == '\n'); 196 | } 197 | } 198 | } 199 | } 200 | 201 | return character != '\0'; 202 | } 203 | 204 | char *util_StringInputBox(unsigned int x, uint8_t y, uint8_t stringLength, uint8_t inputMode, kb_lkey_t exitKey) { 205 | bool keyPressed = false; 206 | bool cursorActive = true; 207 | uint8_t currentOffset = 0; 208 | uint8_t charCount = 0; 209 | 210 | clock_t clockOffset = clock(); 211 | 212 | static char input[MAX_INPUT_LENGTH]; 213 | char character = '\0'; 214 | 215 | for (uint8_t i = 0; i <= stringLength; i++) { 216 | input[i] = '\0'; 217 | } 218 | 219 | fontlib_SetForegroundColor(TEXT_DEFAULT); 220 | 221 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(exitKey)) { 222 | kb_Scan(); 223 | 224 | if (!kb_AnyKey() && keyPressed) { 225 | keyPressed = false; 226 | clockOffset = clock(); 227 | } 228 | 229 | if (kb_AnyKey() && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 16)) { 230 | clockOffset = clock(); 231 | cursorActive = true; 232 | 233 | gfx_SetColor(BACKGROUND); 234 | gfx_FillRectangle_NoClip(x, y, charCount * 7 + 2, 12); 235 | 236 | if (kb_IsDown(kb_KeyClear) || kb_IsDown(exitKey)) { 237 | break; 238 | } else if ((kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyEnter)) && charCount != 0) { 239 | break; 240 | } else if (kb_IsDown(kb_KeyLeft)) { 241 | if (currentOffset) { 242 | currentOffset--; 243 | } 244 | } else if (kb_IsDown(kb_KeyRight)) { 245 | if (currentOffset < charCount) { 246 | currentOffset++; 247 | } 248 | } else if (kb_IsDown(kb_KeyUp)) { 249 | currentOffset = 0; 250 | } else if (kb_IsDown(kb_KeyDown)) { 251 | currentOffset = charCount; 252 | } else if (kb_IsDown(kb_KeyMode)) { // Backspace 253 | if (currentOffset && charCount) { 254 | for (uint8_t i = currentOffset; input[i - 1] != '\0'; i++) { 255 | input[i - 1] = input[i]; 256 | } 257 | 258 | charCount--; 259 | currentOffset--; 260 | } 261 | } else if (kb_IsDown(kb_KeyDel)) { 262 | if (currentOffset < charCount && charCount) { 263 | for (uint8_t i = currentOffset; input[i] != '\0'; i++) { 264 | input[i] = input[i + 1]; 265 | } 266 | 267 | charCount--; 268 | } 269 | } else if (kb_IsDown(kb_KeyAlpha)) { 270 | if (inputMode == INPUT_LOWERCASE) { 271 | inputMode = 0; 272 | } else { 273 | inputMode++; 274 | } 275 | 276 | while (kb_AnyKey()); 277 | } else if (charCount < stringLength - 1) { 278 | if (!keyPressed) { 279 | character = asm_misc_GetCharFromKey(inputMode); 280 | } 281 | 282 | if ((character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9')) { 283 | for (uint8_t i = stringLength - 1; i != currentOffset; i--) { 284 | input[i] = input[i - 1]; 285 | } 286 | 287 | input[currentOffset] = character; 288 | charCount++; 289 | currentOffset++; 290 | } 291 | } 292 | 293 | asm_spi_BeginFrame(); 294 | gfx_SetColor(CURSOR); 295 | gfx_FillRectangle_NoClip(x + (currentOffset * 7), y, 2, 12); // Draw new cursor 296 | fontlib_SetCursorPosition(x + 1, y); 297 | fontlib_DrawString(input); 298 | asm_spi_EndFrame(); 299 | 300 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 301 | } 302 | 303 | if (!keyPressed && clock() - clockOffset > CLOCKS_PER_SEC / 3) { 304 | if (cursorActive) { 305 | gfx_SetColor(CURSOR); 306 | } else { 307 | gfx_SetColor(BACKGROUND); 308 | } 309 | 310 | gfx_FillRectangle_NoClip(x + (currentOffset * 7), y, 2, 12); 311 | 312 | cursorActive = !cursorActive; 313 | clockOffset = clock(); 314 | 315 | asm_spi_EndFrame(); 316 | } 317 | } 318 | 319 | if (kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyEnter)) { 320 | return input; 321 | } 322 | 323 | if (kb_IsDown(kb_KeyClear)) { 324 | while (kb_AnyKey()); 325 | } 326 | 327 | return NULL; 328 | } 329 | 330 | void util_WaitBeforeKeypress(clock_t *clockOffset, bool *keyPressed) { 331 | if (!(*keyPressed)) { 332 | while ((clock() - *clockOffset < CLOCKS_PER_SEC / 3) && kb_AnyKey()) { 333 | kb_Scan(); 334 | } 335 | } 336 | 337 | *keyPressed = true; 338 | *clockOffset = clock(); 339 | } 340 | 341 | bool util_OpenFile(struct context_t *studioContext, char *fileName) { 342 | if (asm_files_ReadFile(fileName)) { 343 | strcpy(studioContext->fileName, fileName); 344 | studioContext->fileIsOpen = true; 345 | studioContext->fileIsSaved = true; 346 | 347 | uint8_t file = ti_Open(studioContext->fileName, "r"); 348 | studioContext->fileSize = ti_GetSize(file); 349 | ti_Close(file); 350 | 351 | studioContext->pageDataStart = (char *)EDIT_BUFFER; 352 | studioContext->rowDataStart = studioContext->pageDataStart; 353 | studioContext->openEOF = (char *)EDIT_BUFFER + studioContext->fileSize - 3; 354 | 355 | studioContext->lineStart = 0; 356 | studioContext->newlineStart = 0; 357 | studioContext->row = 0; 358 | studioContext->column = 0; 359 | 360 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 361 | 362 | asm_files_CountLines(&(studioContext->newlineCount), &(studioContext->totalLines), studioContext->openEOF); 363 | 364 | return true; 365 | } 366 | 367 | return false; 368 | } 369 | -------------------------------------------------------------------------------- /src/edit.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - utility.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "assembler.h" 13 | #include "edit.h" 14 | #include "utility.h" 15 | #include "ui.h" 16 | #include "menu.h" 17 | #include "defines.h" 18 | #include "asm/files.h" 19 | #include "asm/misc.h" 20 | #include "asm/spi.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | void edit_RedrawEditor(struct context_t *studioContext, struct preferences_t *studioPreferences) { 29 | asm_spi_BeginFrame(); 30 | gfx_ZeroScreen(); 31 | 32 | if (studioContext->fileIsOpen) { 33 | ui_DrawUIMain(0, studioContext->totalLines, studioContext->lineStart); 34 | ui_UpdateText(studioContext, studioPreferences, UPDATE_ALL); 35 | ui_DrawCursor(studioContext->row, studioContext->column, true, false); 36 | gfx_SetColor(OUTLINE); 37 | fontlib_SetForegroundColor(TEXT_DEFAULT); 38 | fontlib_SetCursorPosition(312, 0); 39 | fontlib_DrawGlyph("1Aa"[studioContext->inputMode]); 40 | gfx_Rectangle_NoClip(312, 12, 8, 2); 41 | } else { 42 | ui_NoFile(); 43 | } 44 | 45 | asm_spi_EndFrame(); 46 | } 47 | 48 | static void edit_Scroll(struct context_t *studioContext, struct preferences_t *studioPreferences, uint8_t direction) { 49 | gfx_SetClipRegion(0, 0, 310, 223); 50 | 51 | if (direction == UPDATE_TOP) { 52 | gfx_ShiftDown(16); 53 | } else { 54 | gfx_ShiftUp(16); 55 | } 56 | 57 | ui_UpdateText(studioContext, studioPreferences, direction); 58 | gfx_SetClipRegion(0, 0, 320, 240); 59 | ui_DrawScrollbar(312, 14, 209, studioContext->totalLines, studioContext->lineStart, 14); 60 | } 61 | 62 | static bool edit_CursorUp(struct context_t *studioContext) { 63 | if (studioContext->row) { 64 | studioContext->row--; 65 | studioContext->rowDataStart = asm_files_PreviousLine(studioContext->rowDataStart, studioContext->openEOF); 66 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 67 | } else if (studioContext->lineStart) { 68 | studioContext->newlineStart -= ((*(studioContext->pageDataStart - 1) == '\n') || !(studioContext->lineStart)); 69 | studioContext->lineStart--; 70 | studioContext->pageDataStart = asm_files_PreviousLine(studioContext->pageDataStart, studioContext->openEOF); 71 | studioContext->rowDataStart = studioContext->pageDataStart; 72 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 73 | return true; 74 | } else { 75 | studioContext->column = 0; 76 | } 77 | 78 | return false; 79 | } 80 | 81 | static bool edit_CursorDown(struct context_t *studioContext) { 82 | if (studioContext->row < 13 && studioContext->lineStart + studioContext->row + 1 != studioContext->totalLines) { 83 | studioContext->row++; 84 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 85 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 86 | } else if (studioContext->lineStart + 14 < studioContext->totalLines) { 87 | studioContext->lineStart++; 88 | studioContext->pageDataStart = asm_files_NextLine(studioContext->pageDataStart); 89 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 90 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 91 | studioContext->newlineStart += (*(studioContext->pageDataStart - 1) == '\n'); 92 | return true; 93 | } else { 94 | studioContext->column = studioContext->rowLength; 95 | } 96 | 97 | return false; 98 | } 99 | 100 | static bool edit_CursorLeft(struct context_t *studioContext) { 101 | if (studioContext->column) { 102 | studioContext->column--; 103 | } else { 104 | if (studioContext->row) { 105 | studioContext->row--; 106 | studioContext->rowDataStart = asm_files_PreviousLine(studioContext->rowDataStart, studioContext->openEOF); 107 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 108 | studioContext->column = studioContext->rowLength; 109 | } else if (studioContext->lineStart) { 110 | studioContext->lineStart--; 111 | studioContext->pageDataStart = asm_files_PreviousLine(studioContext->pageDataStart, studioContext->openEOF); 112 | studioContext->rowDataStart = studioContext->pageDataStart; 113 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 114 | studioContext->column = studioContext->rowLength; 115 | studioContext->newlineStart -= ((*(studioContext->pageDataStart - 1) == '\n') || !(studioContext->lineStart)); 116 | return true; 117 | } 118 | } 119 | 120 | return false; 121 | } 122 | 123 | static bool edit_CursorRight(struct context_t *studioContext) { 124 | if (studioContext->column < studioContext->rowLength) { 125 | studioContext->column++; 126 | } else { 127 | if (studioContext->row < 13 && studioContext->lineStart + studioContext->row + 1 != studioContext->totalLines) { 128 | studioContext->row++; 129 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 130 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 131 | studioContext->column = 0; 132 | } else if (studioContext->lineStart + 14 < studioContext->totalLines) { 133 | studioContext->lineStart++; 134 | studioContext->pageDataStart = asm_files_NextLine(studioContext->pageDataStart); 135 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 136 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 137 | studioContext->column = 0; 138 | studioContext->newlineStart += (*(studioContext->pageDataStart - 1) == '\n'); 139 | return true; 140 | } 141 | } 142 | 143 | return false; 144 | } 145 | 146 | static void edit_Delete(struct context_t *studioContext) { 147 | if (studioContext->fileIsSaved) { 148 | studioContext->fileIsSaved = false; 149 | } 150 | 151 | asm_files_DeleteChar(studioContext->rowDataStart + studioContext->column, studioContext->openEOF - (studioContext->rowDataStart + studioContext->column - 1)); 152 | studioContext->openEOF--; 153 | studioContext->fileSize--; 154 | 155 | asm_files_CountLines(&(studioContext->newlineCount), &(studioContext->totalLines), studioContext->openEOF); 156 | 157 | if (studioContext->lineStart && studioContext->lineStart + 12 >= studioContext->totalLines - 1) { 158 | studioContext->row++; 159 | studioContext->lineStart--; 160 | studioContext->pageDataStart = asm_files_PreviousLine(studioContext->pageDataStart, studioContext->openEOF); 161 | studioContext->newlineStart -= ((*(studioContext->pageDataStart - 1) == '\n') || !(studioContext->lineStart)); 162 | } 163 | 164 | if (studioContext->rowDataStart != (char *)EDIT_BUFFER && *(studioContext->rowDataStart - 1) != '\n' && *(studioContext->rowDataStart) == '\n') { 165 | studioContext->rowDataStart += 1; 166 | } 167 | 168 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 169 | } 170 | 171 | void edit_OpenEditor(struct context_t *studioContext, struct preferences_t *studioPreferences) { 172 | while (kb_AnyKey()); 173 | 174 | bool keyPressed = false; 175 | bool cursorActive = true; 176 | bool redraw = false; 177 | 178 | uint8_t inputChar = '\0'; 179 | 180 | studioContext->fileIsSaved = true; 181 | 182 | asm_spi_BeginFrame(); 183 | edit_RedrawEditor(studioContext, studioPreferences); 184 | asm_spi_EndFrame(); 185 | 186 | clock_t clockOffset = clock(); // Keep track of an offset for timer stuff 187 | 188 | while (studioContext->fileIsOpen) { 189 | kb_Scan(); 190 | 191 | if (!kb_AnyKey() && keyPressed) { 192 | keyPressed = false; 193 | clockOffset = clock(); 194 | } 195 | 196 | if (kb_AnyKey() && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 32)) { 197 | clockOffset = clock(); 198 | cursorActive = true; 199 | 200 | asm_spi_BeginFrame(); 201 | 202 | if (kb_IsDown(kb_KeyClear)) { 203 | if (!studioContext->fileIsSaved) { 204 | if (menu_Warning(WARNING_UNSAVED)) { 205 | studioContext->fileIsOpen = false; 206 | studioContext->fileIsSaved = true; 207 | } else { 208 | redraw = true; 209 | edit_RedrawEditor(studioContext, studioPreferences); 210 | while (kb_AnyKey()); 211 | } 212 | } else { 213 | studioContext->fileIsOpen = false; 214 | } 215 | } if (kb_IsDown(kb_KeyYequ)) { 216 | menu_File(studioContext, studioPreferences); 217 | edit_RedrawEditor(studioContext, studioPreferences); 218 | while (kb_AnyKey()); 219 | } else if (kb_IsDown(kb_KeyWindow)) { 220 | if (!asm_files_CheckFileExists(studioContext->fileName, OS_TYPE_PRGM) || menu_Warning(WARNING_EXISTS)) { 221 | struct error_t error = assembler_Main(studioContext); 222 | edit_RedrawEditor(studioContext, studioPreferences); 223 | menu_Error(error); 224 | } 225 | 226 | edit_RedrawEditor(studioContext, studioPreferences); 227 | while (kb_AnyKey()); 228 | } else if (kb_IsDown(kb_KeyZoom)) { 229 | menu_Goto(studioContext); 230 | edit_RedrawEditor(studioContext, studioPreferences); 231 | while (kb_AnyKey()); 232 | } else if (kb_IsDown(kb_KeyTrace)) { 233 | char insert = menu_Chars(studioContext); 234 | 235 | if (studioContext->fileSize < MAX_FILE_SIZE && studioContext->fileIsOpen) { 236 | util_InsertChar(insert, studioContext); 237 | } 238 | 239 | edit_RedrawEditor(studioContext, studioPreferences); 240 | while (kb_AnyKey()); 241 | } else if (kb_IsDown(kb_KeyGraph)) { 242 | menu_Settings(studioContext, studioPreferences); 243 | edit_RedrawEditor(studioContext, studioPreferences); 244 | while (kb_AnyKey()); 245 | } else if (kb_IsDown(kb_KeyUp)) { 246 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, true); // Erase old cursor 247 | 248 | if (edit_CursorUp(studioContext)) { 249 | edit_Scroll(studioContext, studioPreferences, UPDATE_TOP); 250 | } 251 | } else if (kb_IsDown(kb_KeyDown)) { 252 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, true); 253 | 254 | if (edit_CursorDown(studioContext)) { 255 | edit_Scroll(studioContext, studioPreferences, UPDATE_BOTTOM); 256 | } 257 | } else if (kb_IsDown(kb_KeyLeft)) { 258 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, true); // Erase old cursor 259 | 260 | if (edit_CursorLeft(studioContext)) { 261 | edit_Scroll(studioContext, studioPreferences, UPDATE_TOP); 262 | } 263 | } else if (kb_IsDown(kb_KeyRight)) { 264 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, true); // Erase old cursor 265 | 266 | if (edit_CursorRight(studioContext)) { 267 | edit_Scroll(studioContext, studioPreferences, UPDATE_BOTTOM); 268 | } 269 | } else if (kb_IsDown(kb_KeyMode)) { 270 | if (!(studioContext->lineStart == 0 && studioContext->row == 0 && studioContext->column == 0)) { 271 | redraw = true; 272 | edit_CursorLeft(studioContext); 273 | edit_Delete(studioContext); 274 | } 275 | } else if (kb_IsDown(kb_KeyDel)) { 276 | if (studioContext->rowDataStart + studioContext->column <= studioContext->openEOF) { 277 | redraw = true; 278 | edit_Delete(studioContext); 279 | } 280 | } else if (kb_IsDown(kb_KeyAlpha)) { 281 | if (studioContext->inputMode == INPUT_LOWERCASE) { 282 | studioContext->inputMode = 0; 283 | } else { 284 | studioContext->inputMode += 1; 285 | } 286 | 287 | gfx_SetColor(BACKGROUND); 288 | gfx_FillRectangle_NoClip(313, 2, 5, 8); 289 | fontlib_SetForegroundColor(TEXT_DEFAULT); 290 | fontlib_SetCursorPosition(312, 0); 291 | fontlib_DrawGlyph("1Aa"[studioContext->inputMode]); 292 | while (kb_AnyKey()); 293 | } else if (studioContext->fileSize < MAX_FILE_SIZE && !redraw) { 294 | if (!keyPressed) { 295 | inputChar = asm_misc_GetCharFromKey(studioContext->inputMode); 296 | } 297 | 298 | redraw = util_InsertChar(inputChar, studioContext); 299 | } 300 | 301 | //dbg_printf("-----\nfileIsOpen: %d\nfileIsSaved: %d\npageDataStart: %p\nrowDataStart: %p\nfileName: %s\nfileSize: %d\nopenEOF: %p\nnewlineCount: %d\ntotalLines: %d\nnewlineStart: %d\nlineStart: %d\nrow: %d\ncolumn: %d\nrowLength: %d\n-----\n", studioContext->fileIsOpen, studioContext->fileIsSaved, studioContext->pageDataStart, studioContext->rowDataStart, studioContext->fileName, studioContext->fileSize, studioContext->openEOF, studioContext->newlineCount, studioContext->totalLines, studioContext->newlineStart, studioContext->lineStart, studioContext->row, studioContext->column, studioContext->rowLength); 302 | 303 | if (studioContext->column > studioContext->rowLength) { 304 | studioContext->column = studioContext->rowLength; 305 | } 306 | 307 | if (!redraw) { 308 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, false); 309 | asm_spi_EndFrame(); 310 | } else { 311 | redraw = false; 312 | edit_RedrawEditor(studioContext, studioPreferences); 313 | } 314 | 315 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 316 | } 317 | 318 | if (clock() - clockOffset > CLOCKS_PER_SEC / 3 && !kb_AnyKey()) { 319 | ui_DrawCursor(studioContext->row, studioContext->column, cursorActive, false); 320 | asm_spi_EndFrame(); 321 | cursorActive = !cursorActive; 322 | clockOffset = clock(); 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /includes/TIOSFLAG.inc: -------------------------------------------------------------------------------- 1 | ?flags := 0D00080h 2 | ;System Flags 3 | ;---------------------------------------------------------------------- 4 | ?ioDelFlag := 0h 5 | ?inDelete := 0 ;1 = DELETE SCREEN 6 | 7 | ?trigFlags := 0h ;Trigonometry mode settings 8 | ?trigDeg := 2 ;1 = degrees, 0=radians 9 | 10 | ?kbdFlags := 0h ;Keyboard scan 11 | ?kbdSCR := 3 ;1=scan code ready 12 | ?kbdKeyPress := 4 ;1=key has been pressed 13 | 14 | ?doneFlags := 0h ;display "Done" 15 | ?donePrgm := 5 ;1=display "Done" after prgm 16 | ;---------------------------------------------------------------------- 17 | ?editFlags := 1h 18 | ?editOpen := 2 ;1=edit buffer is open 19 | 20 | ?ansFlags := 1 21 | ?AnsScroll := 3 ;1=answer can scroll, seems must be reset in order to move about edit buffer 22 | 23 | ?monFlags := 1h ;monitor flags 24 | ?monAbandon := 4 ;1=don't start any long process in put away (#715) 25 | ;---------------------------------------------------------------------- 26 | ?plotFlags := 2h ;plot generation flags 27 | ?plotLoc := 1 ;0=bkup and display, 1=display only 28 | ?plotDisp := 2 ;1=plot is in display, 0=text in display, this also indicates whether graph is being shown or not 29 | 30 | ?grfModeFlags := 2h ;graph mode settings 31 | ?grfFuncM := 4 ;1=function graph 32 | ?grfPolarM := 5 ;1=polar graph 33 | ?grfParamM := 6 ;1=parametric graph 34 | ?grfRecurM := 7 ;1=RECURSION graph 35 | ;---------------------------------------------------------------------- 36 | ?graphFlags := 3h 37 | ?graphDraw := 0 ;0=graph is valid, 1=redraw graph(dirty) 38 | ?graphCursor := 2 39 | ;---------------------------------------------------------------------- 40 | ?grfDBFlags := 4h 41 | ?grfDot := 0 ;0=line, 1=dot 42 | ?grfSimul := 1 ;0=sequential, 1=simultaneous 43 | ?grfGrid := 2 ;0=no grid, 1=grid 44 | ?grfPolar := 3 ;0=rectangular, 1=polar coordinates 45 | ?grfNoCoord := 4 ;0=display coordinates, 1=off 46 | ?grfNoAxis := 5 ;0=axis, 1=no axis 47 | ?grfLabel := 6 ;0=off, 1=axis label 48 | ;---------------------------------------------------------------------- 49 | ?textFlags := 5h ;Text output flags 50 | ?textEraseBelow := 1 ;1=erase line below small char 51 | ?textScrolled := 2 ;1=screen scrolled 52 | ?textInverse := 3 ;1=display inverse bit-map 53 | ?textInsMode := 4 ;0=overstrike, 1=insert mode 54 | ;---------------------------------------------------------------------- 55 | ?ParsFlag := 6h ;PARSER flags 56 | ?listOpen := 5 ; {...} 57 | ?matrixOpen1 := 6 ; [[...]] 58 | ?matrixOpen2 := 7 ; [...] 59 | ;---------------------------------------------------------------------- 60 | ?ParsFlag2 := 7h ;PARSER flags 61 | ?numOP1 := 0 ;1=RESULT IN OP1, 0=NO RESULT 62 | ;---------------------------------------------------------------------- 63 | ?newDispF := 8h ;Derivative mode flags 64 | ?preClrForMode := 0 ;1=HELP BLINK ON MODE SCREEN 65 | ?allowProgTokens := 1 ;1=allow programming tokens to be parsed in BASIC programs 66 | ?progExecuting := 1 67 | 68 | ?apdFlags := 8h ;Automatic power-down 69 | ?apdAble := 2 ;1=APD enabled 70 | ?apdRunning := 3 ;1=APD clock running 71 | ?apdWarmStart := 4 ;1=calculator is turning on from APD or power loss 72 | ;---------------------------------------------------------------------- 73 | ?web_err_mask := 60h 74 | ;---------------------------------------------------------------------- 75 | ?onFlags := 9h ;on key flags 76 | ?parseInput := 1 ;1=parse input when done 77 | ?onRunning := 3 ;1=calculator is running 78 | ?onInterrupt := 4 ;1=on key interrupt request 79 | 80 | ?statFlags := 9h ;statistics flags 81 | ;unknown equ 5 ;unknown 82 | ?statsValid := 6 ;1=stats are valid 83 | ;unknown equ 7 ;unknown 84 | ;---------------------------------------------------------------------- 85 | ?fmtFlags := 0Ah ;numeric format flags 86 | ?fmtExponent := 0 ;1=show exponent, 0=no exponent 87 | ?fmtEng := 1 ;1=engineering notion, 0=scientific 88 | 89 | ?numMode := 0Ah 90 | ?fmtReal := 5 91 | ?fmtRect := 6 92 | ?fmtPolar := 7 93 | 94 | ?realMode := 5 95 | ?rectMode := 6 96 | ?polarMode := 7 97 | 98 | ?fmtBaseMask := 00011100b ; mask to base flags 99 | ?fmtBaseShift := 2 ; offset to base flags 100 | ;---------------------------------------------------------------------- 101 | ?fmtOverride := 0Bh ;copy of fmtFlags with conversion override 102 | ;---------------------------------------------------------------------- 103 | ?fmtEditFlags := 0Ch ;numeric editing flags 104 | ?fmtEdit := 0 ;1=format number for editing 105 | 106 | ?curFlags := 0Ch ;Cursor 107 | ?curAble := 2 ;1=cursor flash is enabled 108 | ?curOn := 3 ;1=cursor is showing 109 | ?curLock := 4 ;1=cursor is locked off 110 | 111 | ?cmdFlags := 0Ch ;command editor flags 112 | ?cmdVirgin := 5 ;1=nothing has been typed in cmd bfr 113 | ?cmdExec := 6 ;1=need to execute a command 114 | ;---------------------------------------------------------------------- 115 | ?appFlags := 0Dh ;application flags 116 | ?appWantIntrpt := 0 ;1=want ON key interrupts 117 | ?appTextSave := 1 ;1=save characters in textShadow 118 | ?appAutoScroll := 2 ;1=auto-scroll text on last line 119 | ?appMenus := 3 ;1=process keys that bring up menus, 0=check Lock menu flag 120 | ?appLockMenus := 4 ;1=ignore menu keys, 0=switch to home screen and bring up menu 121 | ?appCurGraphic := 5 ;1=graphic cursor 122 | ?appCurWord := 6 ;1=text cursor covers entire word 123 | ?appExit := 7 ;1=application handles [EXIT] key itself 124 | 125 | ?appWantIntrptF := 1 126 | ?appTextSaveF := 2 127 | ?appAutoScrollF := 4 128 | ?appMenusF := 8 129 | ?appLockMenusF := 16 130 | ?appCurGraphicF := 32 131 | ?appCurWordF := 64 132 | ?appExitF := 128 133 | ;---------------------------------------------------------------------- 134 | ?rclFlag := 0Eh ;OS recall queue flags 135 | ?enableQueue := 7 ;1 = enable recall queue 136 | ;---------------------------------------------------------------------- 137 | ?seqFlags := 0Fh ;Sequential Graph flags 138 | ?webMode := 0 ;0 = NORMAL SEQ MODE, 1 = WEB MODE 139 | ?webVert := 1 140 | ?sequv := 2 ;U vs V 141 | ?seqvw := 3 ;V vs W 142 | ?sequw := 4 ;U vs W 143 | ;---------------------------------------------------------------------- 144 | ?promptFlags := 11h ;prompt line flags 145 | ?promptEdit := 0 ;1=editing in prompt buffer 146 | ;unknown equ 7 ;unknown 147 | ;---------------------------------------------------------------------- 148 | ?indicFlags := 12h ;Indicator flags 149 | ?indicRun := 0 ;1=run indicator ON 150 | ?indicInUse := 1 ;indicator save area in use=1, free=0 ;resetting will disable 2nd while in _getkey 151 | 152 | ?shiftFlags := 12h ;[2nd] and [ALPHA] flags 153 | ?shift2nd := 3 ;1=[2nd] has been pressed 154 | ?shiftAlpha := 4 ;1=[ALPHA] has been pressed 155 | ?shiftLwrAlph := 5 ;1=lower case, 0=upper case 156 | ?shiftALock := 6 ;1=alpha lock has been pressed 157 | ?shiftKeepAlph := 7 ;1=cannot cancel alpha shift 158 | ;---------------------------------------------------------------------- 159 | ?tblFlags := 13h ;table flags. 160 | ?autoFill := 4 ;1=prompt, 0=fillAuto 161 | ?autoCalc := 5 ;1=prompt, 0=CalcAuto 162 | ?reTable := 6 ;0=table is okay, 1=must recompute table. 163 | ;---------------------------------------------------------------------- 164 | ?sGrFlags := 14h 165 | ?grfSplit := 0 ;1=Split Graph, 0=Normal 166 | ?vertSplit := 1 ;1=Vertical (left-right) Split 167 | ?grfSChanged := 2 ;1=Graph just changed Split <-> normal 168 | ?grfSplitOverride := 3 ;1 = ignore graph split flag if set 169 | ?write_on_graph := 4 ;1 = TEXT OR EQU WRITING TO GRAPH SCREEN 170 | ?g_style_active := 5 ;1 = GRAPH STYLES ARE ENABLED, USE THEM 171 | ?cmp_mod_box := 6 ;1 = DOING MOD BOX PLOT COMPUTATION 172 | ?textWrite := 7 173 | ;---------------------------------------------------------------------- 174 | ?newIndicFlags := 15h 175 | ?extraIndic := 0 176 | ?saIndic := 1 177 | ;3 has something to do with stat/list editor 178 | ;---------------------------------------------------------------------- 179 | ?interruptFlags := 16h 180 | ?secondTimerEnabled := 0 ;1 = second hardware timer enabled 181 | ;---------------------------------------------------------------------- 182 | ?smartFlags := 17h 183 | ?smarter_mask := 3 184 | ?smarter_test := 1 185 | ?smartGraph := 0 186 | ?smartGraph_inv := 1 187 | ;---------------------------------------------------------------------- 188 | ?traceFlags := 18h 189 | ?grfExpr := 0 ;set to hide expression while tracing 190 | ;---------------------------------------------------------------------- 191 | ;There is a flag 19h. 192 | ;---------------------------------------------------------------------- 193 | ?statFlags2 := 1Ah 194 | ?statDiagnosticsOn := 0 ;1 = stat diagnostics on 195 | ?noDelStat := 2 ;1 = don't delete stats 196 | ;---------------------------------------------------------------------- 197 | ?apdFlags2 := 1Bh 198 | ?warmStartInt := 6 ;1 = a warm start is occurning before the next interrupt 199 | ;---------------------------------------------------------------------- 200 | ;There is a flag 1Ch (stats-related). 201 | ;---------------------------------------------------------------------- 202 | ;There is a flag 1Dh. 203 | ;---------------------------------------------------------------------- 204 | ;There is a flag 1Eh. 205 | ;---------------------------------------------------------------------- 206 | ?graphFlags2 := 1Fh 207 | ?splitOverride := 3 ;0 = force full screen with ParseInp, or something 208 | ;---------------------------------------------------------------------- 209 | ?asm_Flag1 := 21h ;ASM CODING 210 | ?asm_Flag2 := 22h ;ASM CODING 211 | ?asm_Flag3 := 23h ;NO LONGER AVAILABLE 212 | ;---------------------------------------------------------------------- 213 | ?arcFlag := 24h 214 | ?checkBatteryLevelFirst := 0 ;1 = check battery levels in Arc_Unarc first and throw error if low 215 | 216 | ?getSendFlg := 24h 217 | ?comFailed := 1 ;1 = Get/Send Communication Failed 218 | 219 | ?selfTestFlag := 24h 220 | ?resetOnPowerOn := 2 ;1 = Force RAM reset when APD disabled on next power on 221 | 222 | ?appLwrCaseFlag := 24h 223 | ?lwrCaseActive := 3 224 | ;---------------------------------------------------------------------- 225 | ?contextFlags := 25h 226 | ?nocxPutAway := 5 ;1 = do not call cxPutAway routine 227 | ;---------------------------------------------------------------------- 228 | ?groupFlags := 26h ;used temporarily in Arc_Unarc 229 | ?inGroup := 1 ;1 = IN GROUP CONTEXT 230 | ?noCompletionByte := 2 ;1 = do not write 0FCh when calling Arc_Unarc, leave as 0FEh 231 | ?noDataWrite := 3 ;1 = do not write data when calling Arc_Unarc, nor size bytes 232 | ?writeSizeBytesOnly := 5 ;1 = only write size bytes when calling Arc_Unarc 233 | ;---------------------------------------------------------------------- 234 | ?statusBarFlags := 27h 235 | ?noStatusBarMode := 7 ; 1 = abort drawing of statusbar mode, like "TEST MODE ENABLED" 236 | ;---------------------------------------------------------------------- 237 | ?APIFlg := 28h 238 | ?appAllowContext := 0 ;app wants context changes to happen 239 | 240 | ?appRunning := 4 ;app is currently running 241 | ?appRetKeyOff := 7 ;1 = GetKey returns kOff when [2nd]+[ON] pressed 242 | ;---------------------------------------------------------------------- 243 | ?apiFlg2 := 29h 244 | ;---------------------------------------------------------------------- 245 | ?apiFlg3 := 2Ah 246 | ;---------------------------------------------------------------------- 247 | ?apiFlg4 := 2Bh 248 | ?cellOverride := 1 ;use cell override 249 | ?fullScrnDraw := 2 ;DRAW INTO LAST ROW/COL OF SCREEN 250 | ;---------------------------------------------------------------------- 251 | ?xapFlag0 := 2Eh ;external app flags, do not use 0,(iy+2Eh) (used by mouse routines) 252 | ?xapFlag1 := 2Fh 253 | ?xapFlag2 := 30h 254 | ?xapFlag3 := 31h 255 | ;---------------------------------------------------------------------- 256 | ?fontFlags := 32h 257 | ?fracDrawLFont := 2 258 | ?fracTallLFont := 3 259 | ?customFont := 7 260 | ;---------------------------------------------------------------------- 261 | ?hookflags1 := 33h ;also scriptFlag, rclFlag2, backGroundLink 262 | ?alt_On := 0 ;run ONSCRPT at startup 263 | ?alt_Off := 1 ;run OFFSCRPT at shutdown 264 | ?useRclQueueEnd := 2 ;1 = external mode 265 | ?ignoreBPLink := 3 ;1 = override flag for link activity hook 266 | ?bPLinkOn := 4 ;1 = link activity hook active 267 | ?enableKeyEcho := 5 ;1 = sends keypresses back to connected calc as remote control packets (with GetCSC vs. GetKey codes...really dumb, TI) 268 | ?noTempDelete := 6 ;1 = do not delete temporary programs at homescreen 269 | ;---------------------------------------------------------------------- 270 | ?hookflags2 := 34h ;also sysHookFlg 271 | ?getCSCHookActive := 0 ;1 = GetCSC hook active 272 | ?libraryHookActive := 1 ;1 = library hook active 273 | ?noHookActive := 2 ;1 = same as 0; never used by OS 274 | ?homescreenHookActive := 4 ;1 = homescreen hook active 275 | ?rawKeyHookActive := 5 ;1 = raw key hook active 276 | ?catalog2HookActive := 6 ;1 = catalog 2 hook active 277 | ?cursorHookActive := 7 ;1 = cursor hook active 278 | ;---------------------------------------------------------------------- 279 | ?hookflags3 := 35h ;also sysHookFlg1 280 | ?tokenHookActive := 0 ;1 = token hook active 281 | ?localizeHookActive := 1 ;1 = localize hook active 282 | ?windowHookActive := 2 ;1 = window hook active 283 | ?graphHookActive := 3 ;1 = graph hook active 284 | ?yEquHookActive := 4 ;1 = Y= hook active 285 | ?fontHookActive := 5 ;1 = font hook active 286 | ?regraphHookActive := 6 ;1 = regraph hook active 287 | ?drawingHookActive := 7 ;1 = drawing hook active 288 | ;---------------------------------------------------------------------- 289 | ?hookflags4 := 36h ;also sysHookFlag2 290 | ?traceHookActive := 0 ;1 = trace hook active 291 | ?parserHookActive := 1 ;1 = parser hook active 292 | ?appChangeHookActive := 2 ;1 = app change hook active 293 | ?catalog1HookActive := 3 ;1 = catalog 1 hook active 294 | ?helpHookActive := 4 ;1 = help hook active 295 | ?cxRedispHookActive := 5 ;1 = cxRedisp hook active 296 | ?menuHookActive := 6 ;1 = menu hook active 297 | ?silentLinkHookActive := 7 ;1 = silent link hook active 298 | ;---------------------------------------------------------------------- 299 | ;hookflags2Override equ 37h ;set corresponding bit to kill iy+35h hook when executing app 300 | ;---------------------------------------------------------------------- 301 | ;hookflags3Override equ 38h ;set corresponding bit to kill iy+36h hook when executing app 302 | ;---------------------------------------------------------------------- 303 | ;hookflags4Override equ 39h ;set corresponding bit to kill iy+37h hook when executing app 304 | ;---------------------------------------------------------------------- 305 | ?hookflags5 := 3Ah 306 | ?usbActivityHookActive := 0 ;1 = USB activity hook active 307 | ;---------------------------------------------------------------------- 308 | ?plotFlag3 := 3Ch 309 | ?bufferOnly := 0 310 | ?useFastCirc := 4 311 | ;---------------------------------------------------------------------- 312 | ?dBKeyFlags := 3Dh 313 | ?keyDefaultsF := 6 ;1 = GetKey returns extended keycodes with TI-Keyboard 314 | ;---------------------------------------------------------------------- 315 | ?silentLinkFlags := 3Eh 316 | ?silentLinkActive := 0 ;1 = SE/84+ silent link is active 317 | 318 | ?extraHookFlags := 3Eh 319 | ?checkCatalog2HookVer := 3 ;1 = check catalog 2 hook's version before executing it (and error or take other action if so) 320 | ?openLibActive := 4 ;1 = OpenLib( was successfully called on a Flash application (ExecLib will error if zero) 321 | ;---------------------------------------------------------------------- 322 | ?clockFlags := 3Fh 323 | ?notMDYMode := 0 ;0 = M/D/Y format 324 | ?isYMDMode := 1 ;1 = Y/M/D format 325 | ?is24Hour := 2 ;1 = clock in 24 hour mode 326 | ?inAfternoon := 3 ;1 = current time is in afternoon (PM) (I think) 327 | ?useTokensInString := 4 ;1 = use tokens instead of characters when displaying clock as string (for getTmStr and getDtStr vs. MODE screen) (keep this reset) 328 | ?displayClock := 5 ;1 = display clock (this is set every second, reset otherwise) 329 | ?clockOn := 6 ;1 = clock on 330 | ;---------------------------------------------------------------------- 331 | ?mathprintFlags := 44h 332 | ?mathprintEnabled := 5 ;1 = use mathprint styling 333 | ;---------------------------------------------------------------------- 334 | ?InitialBootMenuFlags := 45h 335 | ?statWizards := 2 ; 1 = stat wizards off 336 | ?dispinitialBootMenu := 4 ; 1 = don't display the initial boot menu 337 | ;---------------------------------------------------------------------- 338 | ?backlightFlags := 46h 339 | ?restoreBrightness := 0 ;1 = restore lcd brightness when needed 340 | ;---------------------------------------------------------------------- 341 | ?asymptoteFlags := 47h 342 | ?detectAsymptotes := 0 ; 1 = detect asymptotes off 343 | ;---------------------------------------------------------------------- 344 | ?fracFlags := 48h 345 | ?mixedFractions := 0 ; 1 = display mixed fractions (Un/d) 346 | ?answersAuto := 1 ; 1 = display decimal answers 347 | ;---------------------------------------------------------------------- 348 | ?grFlags := 4Ah 349 | ?drawGrLbls := 0 ;1 = don't draw Graph Labels (this is usually reset anyway) 350 | 351 | ?putMapFlags := 4Ah 352 | ?usePixelShadow2 := 3 ;1 = use pixelshadow2, not pixelshadow 353 | ?putMapUseColor := 4 ;1 = use custom color 354 | ;---------------------------------------------------------------------- 355 | ?graphDispFlags := 4Bh 356 | ?backgroundValid := 4 ;1 = items in graph background are still valid 357 | ;---------------------------------------------------------------------- 358 | ?graphBgFlags := 4Fh 359 | ?drawGrBackground := 0 ; 1 = graphBG is a solid color or an image that exists 360 | ;---------------------------------------------------------------------- 361 | -------------------------------------------------------------------------------- /src/assembler.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - assembler.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "assembler.h" 13 | #include "highlight.h" 14 | #include "parser.h" 15 | #include "utility.h" 16 | #include "asm/files.h" 17 | #include "asm/lexer.h" 18 | #include "asm/misc.h" 19 | #include "asm/spi.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | bool assembler_IsChar(char *string) { 26 | if (*(++string) == '\\') { 27 | string++; 28 | } 29 | 30 | string++; 31 | 32 | return *string == '\'' || *string == '\"'; 33 | } 34 | 35 | static void assembler_SanitizeLine(char *line, char *string, char *endOfFile, bool pass2) { 36 | bool inQuotes = false; 37 | uint8_t nestParens = 0; 38 | bool inInstruction = false; 39 | char *stringEnd; 40 | char *start = line; 41 | 42 | while (*string != '\n' && string <= endOfFile) { 43 | if ((*string == '\'' || *string == '\"') && *(string - 1) != '\\' && (!assembler_IsChar(string) || pass2)) { 44 | inQuotes = !inQuotes; 45 | 46 | if (!inQuotes) { // Just exited quotes 47 | *line = *string; 48 | string++; 49 | line++; 50 | continue; 51 | } 52 | } else if ((*string == '\'' || *string == '\"') && *(string - 1) != '\\') { 53 | if (*line != '#') { 54 | *(line++) = '#'; 55 | } 56 | 57 | string++; 58 | string += (*string == '\\') ? 2 : 1; 59 | } 60 | 61 | if (inQuotes) { 62 | *line = *string; 63 | string++; 64 | line++; 65 | continue; 66 | } 67 | 68 | if (*string == ' ') { 69 | string++; 70 | continue; 71 | } else if (*string == ';') { 72 | break; 73 | } 74 | 75 | stringEnd = util_GetStringEnd(string, endOfFile, false); 76 | 77 | uint8_t lexerType = asm_lexer_TokType(string, stringEnd); 78 | 79 | if (lexerType == TEXT_INSTRUCTION) { 80 | inInstruction = true; 81 | strcpy(line, hlight_GetTokenString(string, stringEnd)); 82 | line += stringEnd - string; 83 | string = stringEnd; 84 | stringEnd = util_GetStringEnd(string, endOfFile, false); 85 | 86 | if (*string != '.' || !pass2) { 87 | *(line++) = ' '; // Separate arguments 88 | } 89 | } else if (lexerType == TEXT_REGISTER) { 90 | strcpy(line, hlight_GetTokenString(string, stringEnd)); 91 | line += stringEnd - string; 92 | string = stringEnd; 93 | 94 | if (!pass2 && *(line - 2) == 'i' && ((*(line - 3) == '(' && *start != 'j') || (*(start + 1) == 'e' && *(line - 3) == ','))) { 95 | *(line++) = '#'; 96 | } 97 | } else if (inInstruction && lexerType == TEXT_MODIFIER) { 98 | if (pass2) { 99 | strcpy(line, hlight_GetTokenString(string, stringEnd)); 100 | line += 4; 101 | *(line++) = ' '; // Separate arguments 102 | } 103 | 104 | string = stringEnd; 105 | } else if ((!strncmp(string, ":=", 2) || !strncasecmp(string, "equ", 3) || !strncasecmp(string, ".equ", 4)) && *(string - 1) == ' ') { 106 | *(line++) = ' '; // Separate equates 107 | 108 | if (*string == ':') { // This gets separated when tokenized 109 | strcpy(line, ":="); 110 | line += 2; 111 | string += 2; 112 | } else { 113 | strcpy(line, hlight_GetTokenString(string, stringEnd)); 114 | line += stringEnd - string; 115 | string = stringEnd; 116 | } 117 | 118 | *(line++) = ' '; 119 | } else if (inInstruction) { 120 | if (*string == ',' || *string == '(' || *string == ')') { 121 | if (*string == ')') { 122 | nestParens--; 123 | } 124 | 125 | if (!nestParens || pass2) { 126 | *(line++) = *string; 127 | } else if (*(line - 1) != '#') { 128 | *(line++) = '#'; 129 | } 130 | 131 | if (*string == '(') { 132 | nestParens++; 133 | } 134 | } else if (pass2) { 135 | strncpy(line, string, stringEnd - string); 136 | line += stringEnd - string; 137 | string = stringEnd; 138 | } else if (*(line - 1) != '#') { 139 | *(line++) = '#'; 140 | } 141 | 142 | string = stringEnd; 143 | } else { 144 | strncpy(line, string, stringEnd - string); 145 | line += stringEnd - string; 146 | string = stringEnd; 147 | } 148 | } 149 | 150 | if (*(line - 1) == ' ') { 151 | line--; 152 | } 153 | 154 | *line = '\0'; 155 | } 156 | 157 | static bool assembler_IsLabel(char *line) { 158 | while (*line != '\0') { 159 | if (*line == ' ') { 160 | return false; 161 | } else if (*line == ':') { 162 | return true; 163 | } 164 | 165 | line++; 166 | } 167 | 168 | return false; 169 | } 170 | 171 | static bool assembler_IsEquate(char *line) { 172 | while (*line != ' ' && *line != '\0') { 173 | line++; 174 | } 175 | 176 | if (*line == ' ') { 177 | line++; 178 | return (!strncmp(line, ":=", 2) || !strncasecmp(line, "equ", 3) || !strncasecmp(line, ".equ", 4)); 179 | } 180 | 181 | return false; 182 | } 183 | 184 | static unsigned int assembler_GetDataSize(char *data) { 185 | if (*data != 'd') { 186 | return 0; 187 | } 188 | 189 | data++; 190 | uint8_t perData = 0; 191 | 192 | switch (*data) { 193 | case 'b': 194 | perData = 1; 195 | break; 196 | case 'w': 197 | perData = 2; 198 | break; 199 | case 'l': 200 | perData = 3; 201 | break; 202 | case 'd': 203 | perData = 4; 204 | break; 205 | default: 206 | return 0; 207 | } 208 | 209 | unsigned int size = 0; 210 | data += 2; 211 | 212 | while (*data != '\0') { 213 | if (*data == '#') { 214 | size += perData; 215 | } else if (*data == '\"' || *data == '\'') { 216 | unsigned int tempSize = 0; 217 | data++; 218 | 219 | while (*data != '\"' && *data != '\'') { 220 | if (*data == '\0') { 221 | return 0; 222 | } 223 | 224 | if (*data == '\\') { 225 | data++; 226 | } 227 | 228 | tempSize++; 229 | data++; 230 | } 231 | 232 | if (tempSize % perData) { 233 | tempSize += perData - (tempSize % perData); 234 | } 235 | 236 | size += tempSize; 237 | } 238 | 239 | data++; 240 | } 241 | 242 | dbg_printf("\nSize: %u\n", size); 243 | 244 | return size; 245 | } 246 | 247 | uint8_t assembler_WriteData(char *output, char *line) { 248 | uint8_t error = ERROR_SUCCESS; 249 | line++; 250 | uint8_t perData = 0; 251 | 252 | switch (*line) { 253 | case 'b': 254 | perData = 1; 255 | break; 256 | case 'w': 257 | perData = 2; 258 | break; 259 | case 'l': 260 | perData = 3; 261 | break; 262 | case 'd': 263 | perData = 4; 264 | } 265 | 266 | line++; 267 | static char data[MAX_LINE_LENGTH_ASM - 3]; 268 | 269 | while (*line != '\0') { 270 | line++; 271 | uint8_t i = 0; 272 | 273 | if (*line == '\"' || *line == '\'') { 274 | line++; 275 | 276 | while (*line != '\"' && *line != '\'') { 277 | if (*line == '\\') { 278 | line++; 279 | } 280 | 281 | *(output++) = *(line++); 282 | } 283 | 284 | line++; 285 | } else { 286 | while (*line != '\0' && *line != ',') { 287 | data[i++] = *(line++); 288 | } 289 | 290 | data[i] = '\0'; 291 | 292 | unsigned long result = parser_Eval(data, &error); 293 | 294 | switch (perData) { 295 | case 1: 296 | *(uint8_t *)output = (uint8_t)result; 297 | break; 298 | case 2: 299 | *(uint16_t *)output = (uint16_t)result; 300 | break; 301 | case 3: 302 | *(unsigned int *)output = (unsigned int)result; 303 | break; 304 | case 4: 305 | *(unsigned long *)output = (unsigned long)result; 306 | break; 307 | } 308 | 309 | output += perData; 310 | } 311 | } 312 | 313 | dbg_printf("%p\n", output); 314 | 315 | return error; 316 | } 317 | 318 | static uint8_t assembler_PutArgs(char *output, char *line, struct opcode_t *opcode, uint8_t suffix) { 319 | bool relative = false; 320 | bool bit = false; 321 | 322 | if ((opcode >= &asm_opcodes_AfterDDCB || (opcode >= &asm_opcodes_AfterCB && opcode < &asm_opcodes_AfterDD))) { 323 | bit = (line[0] == 'b' || line[1] == 'e'); 324 | } else if (!strncmp(line, "djnz", 4) || (*line == 'j' && *(line + 1) == 'r')) { 325 | relative = true; 326 | } 327 | 328 | while (*line != ' ' && *line != '\0') { 329 | line++; 330 | } 331 | 332 | if (*line == '\0') { 333 | return ERROR_SUCCESS; 334 | } 335 | 336 | line++; 337 | char *tokEnd = NULL; 338 | char *endOfLine = line + strlen(line) - 1; 339 | static char data[MAX_LINE_LENGTH_ASM - 4]; 340 | memset(data, 0, MAX_LINE_LENGTH_ASM - 4); 341 | uint8_t error = ERROR_SUCCESS; 342 | uint8_t nestParens = 0; 343 | uint8_t dataOffset = 0; 344 | 345 | while (line <= endOfLine + 1) { 346 | tokEnd = util_GetStringEnd(line, endOfLine, false); 347 | 348 | if (asm_lexer_TokType(line, tokEnd) == TEXT_REGISTER) { 349 | line = tokEnd; 350 | } else if (*line == '(') { 351 | if (nestParens) { 352 | data[dataOffset++] = *line; 353 | } 354 | 355 | nestParens++; 356 | line++; 357 | } else if (*line == '+' && !dataOffset) { 358 | line++; 359 | } else if (*line == '\0' || *line == ')' || *line == ',') { 360 | if (nestParens && *line == ')') { 361 | nestParens--; 362 | 363 | if (nestParens) { 364 | data[dataOffset++] = *line; 365 | } 366 | } 367 | 368 | if (!nestParens && dataOffset) { 369 | unsigned long arg = parser_Eval(data, &error); 370 | memset(data, 0, MAX_LINE_LENGTH_ASM - 4); 371 | dataOffset = 0; 372 | 373 | if (opcode->size == 4) { 374 | if (suffix == SUFFIX_SIS || suffix == SUFFIX_LIS) { 375 | *(uint16_t *)(output + 1) = (uint16_t)arg; 376 | } else { 377 | *(unsigned int *)(output + 1) = (unsigned int)arg; 378 | } 379 | 380 | return error; 381 | } else if (opcode->size == 3) { 382 | *(uint8_t *)(++output) = (uint8_t)arg; 383 | } else if (opcode->size == 2) { 384 | if (relative) { 385 | arg = ((uint8_t *)arg - (os_userMem + ((uint8_t *)output - OUTPUT))); 386 | 387 | if ((int)arg < -128 || (int)arg > 127) { 388 | error = ERROR_RANGE; 389 | } 390 | 391 | *(int8_t *)(output + 1) = (int8_t)arg; 392 | dbg_printf("\nRelative: %d\n", *(int8_t *)(output + 1)); 393 | } else { 394 | *(uint8_t *)(output + 1) = (uint8_t)arg; 395 | } 396 | 397 | return error; 398 | } else if (bit) { 399 | if ((uint8_t)arg >= 8) { 400 | error = ERROR_RANGE; 401 | } 402 | 403 | *(uint8_t *)output |= (uint8_t)arg << 3; 404 | 405 | if (opcode < &asm_opcodes_AfterDDCB) { 406 | return error; 407 | } 408 | 409 | bit = false; 410 | } else if (opcode >= &asm_opcodes_AfterDDCB) { 411 | if ((int)arg < -128 || (int)arg > 127) { 412 | error = ERROR_RANGE; 413 | } 414 | 415 | *(uint8_t *)(output - 1) = (uint8_t)arg; 416 | return error; 417 | } 418 | } 419 | 420 | line++; 421 | } else { 422 | strncpy(data + dataOffset, line, tokEnd - line); 423 | dataOffset += tokEnd - line; 424 | line = tokEnd; 425 | } 426 | } 427 | 428 | return error; 429 | } 430 | 431 | static unsigned long assembler_ReserveBytes(char *line, char *string, char *endOfFile, uint8_t *error) { 432 | if (*line != 'r') { 433 | return 0; 434 | } 435 | 436 | uint8_t perData = 0; 437 | 438 | switch (*(line + 1)) { 439 | case 'b': 440 | perData = 1; 441 | break; 442 | case 'w': 443 | perData = 2; 444 | break; 445 | case 'l': 446 | perData = 3; 447 | break; 448 | case 'd': 449 | perData = 4; 450 | break; 451 | default: 452 | return 0; 453 | } 454 | 455 | if (strncmp(" #", line + 2, 3)) { 456 | return 0; 457 | } 458 | 459 | memset(line, 0, MAX_LINE_LENGTH_ASM); 460 | 461 | assembler_SanitizeLine(line, string, endOfFile, true); 462 | 463 | return perData * parser_Eval(line + 3, error); 464 | } 465 | 466 | static uint8_t assembler_GetSuffix(char *line, char *string, char *endOfFile, uint8_t *error) { 467 | assembler_SanitizeLine(line, string, endOfFile, true); 468 | 469 | uint8_t i = 0; 470 | 471 | for (; line[i] != '.'; i++) { 472 | if (line[i] == ' ' || line[i] == '\0') { 473 | return NO_SUFFIX; 474 | } 475 | } 476 | 477 | i++; 478 | 479 | if (line[i] == 's') { 480 | if (line[i + 2] == 's') { 481 | return SUFFIX_SIS; 482 | } else if (line[i + 2] == 'l') { 483 | return SUFFIX_SIL; 484 | } 485 | } else if (line[i] == 'l') { 486 | if (line[i + 2] == 's') { 487 | return SUFFIX_LIS; 488 | } else if (line[i + 2] == 'l') { 489 | return SUFFIX_LIL; 490 | } 491 | } 492 | 493 | *error = ERROR_INVAL_TOK; 494 | 495 | return NO_SUFFIX; 496 | } 497 | 498 | struct error_t assembler_Main(struct context_t *studioContext) { 499 | asm_spi_BeginFrame(); 500 | gfx_SetColor(OUTLINE); 501 | gfx_FillRectangle_NoClip(102, 101, 106, 20); 502 | gfx_SetColor(BACKGROUND); 503 | gfx_FillRectangle_NoClip(104, 103, 102, 16); 504 | fontlib_SetForegroundColor(TEXT_DEFAULT); 505 | fontlib_SetCursorPosition(107, 105); 506 | fontlib_DrawString("In progress..."); 507 | asm_spi_EndFrame(); 508 | 509 | asm_spi_BeginFrame(); // Stop display updates since we use the other buffer 510 | asm_misc_ClearBuffer(OUTPUT); 511 | memset((void *)SYMBOL_TABLE, '\0', sizeof(char) * MAX_SYMBOL_TABLE); 512 | 513 | *(char *)(SYMBOL_TABLE) = '$'; 514 | *(uint8_t *)(SYMBOL_TABLE + 2) = 3; 515 | 516 | char *string = (char *)EDIT_BUFFER; 517 | static char line[MAX_LINE_LENGTH_ASM]; 518 | 519 | void *offset = (void *)os_userMem; 520 | void *symbolEntry = (void *)SYMBOL_TABLE + sizeof(char) * 3 + sizeof(void *); 521 | dbg_printf("Symbol Table Start: %p\n", symbolEntry); 522 | 523 | struct error_t error = {0, ERROR_SUCCESS}; 524 | unsigned int result; // Use this for values we have to save occasionally 525 | struct opcode_t *opcode; 526 | 527 | while (string <= studioContext->openEOF) { 528 | *(void **)(SYMBOL_TABLE + 3) = offset; // Update $ equate 529 | error.line += 1; 530 | assembler_SanitizeLine(line, string, studioContext->openEOF, false); 531 | 532 | dbg_printf("%s |", line); 533 | 534 | if (assembler_IsLabel(line)) { 535 | dbg_printf("Label @ Offset %p | ", offset); 536 | strncpy(symbolEntry, line, strlen(line) - 1); // Skip ':' at the end of label names 537 | symbolEntry += strlen(line); // One extra byte for '\0' 538 | *(uint8_t *)symbolEntry = 3; 539 | symbolEntry++; 540 | *(void **)symbolEntry = offset; 541 | symbolEntry += 3; 542 | } else if (assembler_IsEquate(line)) { 543 | dbg_printf("Equate @ Table %p | ", symbolEntry); 544 | char *lineCurChar = line; 545 | 546 | for (; *lineCurChar != ' '; lineCurChar++); 547 | 548 | strncpy(symbolEntry, line, lineCurChar - line); 549 | symbolEntry += lineCurChar - line + 1; 550 | lineCurChar++; 551 | 552 | for (; *lineCurChar != ' '; lineCurChar++); 553 | 554 | *(uint8_t *)symbolEntry = sizeof(unsigned long); 555 | symbolEntry++; 556 | *(unsigned long *)symbolEntry = parser_Eval(++lineCurChar, &(error.code)); 557 | 558 | symbolEntry += sizeof(long); 559 | } else if ((result = assembler_GetDataSize(line))) { 560 | offset += result; 561 | } else if ((result = assembler_ReserveBytes(line, string, studioContext->openEOF, &(error.code)))) { 562 | offset += result; 563 | } else if ((opcode = asm_misc_FindOpcode(line))) { 564 | offset += opcode->size; 565 | dbg_printf("Size %u", opcode->size); 566 | 567 | if (opcode >= &asm_opcodes_AfterCB && opcode < &asm_opcodes_AfterDDCB) { 568 | offset++; 569 | dbg_printf(" (+ 1)"); 570 | } else if (opcode >= &asm_opcodes_AfterDDCB) { 571 | offset += 3; 572 | dbg_printf(" (+ 3)"); 573 | } 574 | 575 | if ((result = assembler_GetSuffix(line, string, studioContext->openEOF, &(error.code)))) { 576 | if (result == SUFFIX_LIL || result == SUFFIX_SIL) { 577 | offset++; 578 | } else if (opcode->size != 4) { 579 | offset++; 580 | } 581 | } 582 | 583 | dbg_printf(" | "); 584 | // Check table location to properly adjust for size 585 | } else if (*line != '\0') { 586 | error.code = ERROR_INVAL_TOK; 587 | return error; 588 | } 589 | 590 | if (error.code) { 591 | return error; 592 | } 593 | 594 | if (symbolEntry > (void *)SYMBOL_TABLE + MAX_SYMBOL_TABLE) { 595 | error.code = ERROR_MAX_SYMBOLS; 596 | return error; 597 | } 598 | 599 | while (*string != '\n' && string <= studioContext->openEOF) { 600 | string++; 601 | } 602 | 603 | string++; 604 | 605 | dbg_printf("%s\n", line); 606 | } 607 | 608 | error.line = 0; 609 | 610 | if (offset - (void *)os_userMem + 2 > MAX_FILE_SIZE) { 611 | error.code = ERROR_TOO_LARGE; 612 | return error; 613 | } 614 | 615 | char *output = (char *)(OUTPUT + 2); 616 | string = (char *)EDIT_BUFFER; 617 | 618 | strcpy((char *)OUTPUT, OUTPUT_HEADER); 619 | 620 | while (string <= studioContext->openEOF) { 621 | *(void **)(SYMBOL_TABLE + 3) = os_userMem + ((uint8_t *)output - OUTPUT) - 2; 622 | error.line += 1; 623 | assembler_SanitizeLine(line, string, studioContext->openEOF, false); 624 | 625 | dbg_printf("%s |", line); 626 | 627 | if ((result = assembler_GetDataSize(line))) { 628 | assembler_SanitizeLine(line, string, studioContext->openEOF, true); 629 | error.code = assembler_WriteData(output, line); 630 | 631 | output += result; 632 | } else if ((result = assembler_ReserveBytes(line, string, studioContext->openEOF, &(error.code)))) { 633 | output += result; 634 | } else if ((opcode = asm_misc_FindOpcode(line))) { 635 | dbg_printf("\nOutput: %p\n", output); 636 | assembler_SanitizeLine(line, string, studioContext->openEOF, true); 637 | 638 | switch ((result = assembler_GetSuffix(line, string, studioContext->openEOF, &(error.code)))) { 639 | case SUFFIX_SIS: 640 | *(output++) = 0x40; 641 | break; 642 | case SUFFIX_LIS: 643 | *(output++) = 0x49; 644 | break; 645 | case SUFFIX_SIL: 646 | *(output++) = 0x52; 647 | break; 648 | case SUFFIX_LIL: 649 | *(output++) = 0x5B; 650 | break; 651 | default: 652 | break; 653 | } 654 | 655 | if (opcode >= &asm_opcodes_AfterCB && opcode < &asm_opcodes_AfterDD) { 656 | *(output++) = 0xCB; 657 | } else if (opcode >= &asm_opcodes_AfterDD && opcode < &asm_opcodes_AfterED) { 658 | *(output++) = 0xDD; 659 | } else if (opcode >= &asm_opcodes_AfterED && opcode < &asm_opcodes_AfterFD) { 660 | *(output++) = 0xED; 661 | } else if (opcode >= &asm_opcodes_AfterFD && opcode < &asm_opcodes_AfterDDCB) { 662 | *(output++) = 0xFD; 663 | } else if (opcode >= &asm_opcodes_AfterDDCB && opcode < &asm_opcodes_AfterFDCB) { 664 | *(output++) = 0xDD; 665 | *(output++) = 0xCB; 666 | output++; 667 | } else if (opcode >= &asm_opcodes_AfterFDCB) { 668 | *(output++) = 0xFD; 669 | *(output++) = 0xCB; 670 | output++; 671 | } 672 | 673 | memcpy(output, &opcode->data, opcode->size); 674 | error.code = assembler_PutArgs(output, line, opcode, result); 675 | 676 | output += ((result == SUFFIX_SIS || result == SUFFIX_LIS) && opcode->size == 4) ? opcode->size - 1 : opcode->size; 677 | dbg_printf("Size %u", opcode->size); 678 | 679 | dbg_printf(" | "); 680 | // Check table location to properly adjust for size 681 | } 682 | 683 | if (error.code) { 684 | return error; 685 | } 686 | 687 | while (*string != '\n' && string <= studioContext->openEOF) { 688 | string++; 689 | } 690 | 691 | string++; 692 | } 693 | 694 | error.line = 0; 695 | error.code = asm_files_CreateProg(studioContext->fileSize, studioContext->fileName); 696 | 697 | while (kb_AnyKey()); 698 | asm_spi_EndFrame(); 699 | return error; 700 | } 701 | -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------- 3 | * 4 | * eZ80 Studio Source Code - menu.c 5 | * By RoccoLox Programs and TIny_Hacker 6 | * Copyright 2022 - 2025 7 | * License: GPL-3.0 8 | * 9 | * -------------------------------------- 10 | **/ 11 | 12 | #include "defines.h" 13 | #include "edit.h" 14 | #include "menu.h" 15 | #include "utility.h" 16 | #include "ui.h" 17 | #include "asm/files.h" 18 | #include "asm/misc.h" 19 | #include "asm/spi.h" 20 | #include "gfx/gfx.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | void menu_Error(struct error_t error) { 30 | asm_spi_BeginFrame(); 31 | gfx_SetColor(OUTLINE); 32 | gfx_FillRectangle_NoClip(81, 94, 148, 34); 33 | gfx_SetColor(BACKGROUND); 34 | gfx_FillRectangle_NoClip(83, 110, 144, 16); 35 | fontlib_SetForegroundColor(TEXT_DEFAULT); 36 | 37 | if (error.code) { 38 | if (error.line) { 39 | fontlib_SetCursorPosition(96, 96); 40 | } else { 41 | fontlib_SetCursorPosition(137, 96); 42 | } 43 | 44 | fontlib_DrawString("Error"); 45 | 46 | if (error.line) { 47 | fontlib_DrawString(" (Line "); 48 | fontlib_DrawUInt(error.line, 4); 49 | fontlib_DrawGlyph(')'); 50 | } 51 | } else { 52 | fontlib_SetCursorPosition(130, 96); 53 | fontlib_DrawString("Success"); 54 | } 55 | 56 | switch (error.code) { 57 | case ERROR_SUCCESS: 58 | fontlib_SetCursorPosition(90, 112); 59 | fontlib_DrawString("Operation complete."); 60 | break; 61 | case ERROR_UNKNOWN: 62 | fontlib_SetCursorPosition(128, 112); 63 | fontlib_DrawString("Unknown."); 64 | break; 65 | case ERROR_NO_MEM: 66 | fontlib_SetCursorPosition(93, 112); 67 | fontlib_DrawString("Not enough memory."); 68 | break; 69 | case ERROR_INVAL_TOK: 70 | fontlib_SetCursorPosition(86, 112); 71 | fontlib_DrawString("Invalid instruction."); 72 | break; 73 | case ERROR_INVAL_SYMBOL: 74 | fontlib_SetCursorPosition(103, 112); 75 | fontlib_DrawString("Invalid symbol."); 76 | break; 77 | case ERROR_MAX_SYMBOLS: 78 | fontlib_SetCursorPosition(96, 112); 79 | fontlib_DrawString("Too many symbols."); 80 | break; 81 | case ERROR_TOO_LARGE: 82 | fontlib_SetCursorPosition(96, 112); 83 | fontlib_DrawString("Output too large."); 84 | break; 85 | case ERROR_RANGE: 86 | fontlib_SetCursorPosition(111, 112); 87 | fontlib_DrawString("Out of range."); 88 | break; 89 | default: 90 | break; 91 | } 92 | 93 | asm_spi_EndFrame(); 94 | 95 | while(kb_AnyKey()); 96 | while (!kb_IsDown(kb_KeyClear)) { 97 | kb_Scan(); 98 | } 99 | } 100 | 101 | void menu_YesNoRedraw(bool returnVal, unsigned int x, uint8_t y, uint8_t buttonWidth) { 102 | asm_spi_BeginFrame(); 103 | gfx_SetColor(BACKGROUND); 104 | gfx_FillRectangle_NoClip(x, y, buttonWidth * 2 + 2, 16); 105 | gfx_SetColor(OUTLINE); 106 | gfx_FillRectangle_NoClip(x + (buttonWidth + 2) * !returnVal, y, buttonWidth, 16); 107 | fontlib_SetCursorPosition(x + (buttonWidth - 21) / 2, y + 2); 108 | fontlib_DrawString("Yes"); 109 | fontlib_SetCursorPosition(x + buttonWidth + 2 + (buttonWidth - 14) / 2, y + 2); 110 | fontlib_DrawString("No"); 111 | asm_spi_EndFrame(); 112 | } 113 | 114 | bool menu_YesNo(unsigned int x, uint8_t y, uint8_t buttonWidth) { 115 | bool returnVal = true; 116 | menu_YesNoRedraw(returnVal, x, y, buttonWidth); 117 | 118 | while(kb_AnyKey()); 119 | 120 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_KeyEnter) && !kb_IsDown(kb_Key2nd)) { 121 | kb_Scan(); 122 | 123 | if (kb_IsDown(kb_KeyLeft) || kb_IsDown(kb_KeyRight)) { 124 | returnVal = !returnVal; 125 | menu_YesNoRedraw(returnVal, x, y, buttonWidth); 126 | while(kb_AnyKey()); 127 | } 128 | } 129 | 130 | if (kb_IsDown(kb_KeyClear)) { 131 | while(kb_AnyKey()); 132 | return false; 133 | } 134 | 135 | while(kb_AnyKey()); 136 | 137 | return returnVal; 138 | } 139 | 140 | bool menu_Warning(uint8_t warning) { 141 | asm_spi_BeginFrame(); 142 | gfx_SetColor(OUTLINE); 143 | gfx_FillRectangle_NoClip(80, 68, 150, 87); 144 | gfx_SetColor(BACKGROUND); 145 | gfx_FillRectangle_NoClip(82, 84, 146, 69); 146 | fontlib_SetForegroundColor(TEXT_DEFAULT); 147 | fontlib_SetCursorPosition(130, 70); 148 | fontlib_DrawString("Warning"); 149 | 150 | switch (warning) { 151 | case WARNING_UNSAVED: 152 | fontlib_SetCursorPosition(85, 85); 153 | fontlib_DrawString("The currently opened"); 154 | fontlib_SetCursorPosition(85, 96); 155 | fontlib_DrawString("file has unsaved"); 156 | fontlib_SetCursorPosition(85, 108); 157 | fontlib_DrawString("changes. Do you wish"); 158 | fontlib_SetCursorPosition(85, 121); 159 | fontlib_DrawString("to discard them?"); 160 | break; 161 | case WARNING_EXISTS: 162 | fontlib_SetCursorPosition(85, 85); 163 | fontlib_DrawString("A program with this"); 164 | fontlib_SetCursorPosition(85, 96); 165 | fontlib_DrawString("name already exists."); 166 | fontlib_SetCursorPosition(85, 108); 167 | fontlib_DrawString("Do you wish to"); 168 | fontlib_SetCursorPosition(85, 121); 169 | fontlib_DrawString("overwrite it?"); 170 | break; 171 | } 172 | 173 | asm_spi_EndFrame(); 174 | 175 | return menu_YesNo(83, 136, 71); 176 | } 177 | 178 | static bool menu_MiniMenu(bool *initialOption, unsigned int x, uint8_t y, unsigned int width, uint8_t height, char *option1, char *option2) { 179 | bool optionSelected = !(*initialOption); 180 | ui_DrawMenuBox(x, y, width, height, optionSelected, 2, option1, option2); 181 | 182 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_KeyEnter) && !kb_IsDown(kb_Key2nd) && !kb_IsDown(kb_KeyLeft)) { 183 | kb_Scan(); 184 | 185 | if (kb_IsDown(kb_KeyUp) || kb_IsDown(kb_KeyDown)) { 186 | optionSelected = !optionSelected; 187 | ui_DrawMenuBox(x, y, width, height, optionSelected, 2, option1, option2); 188 | while (kb_AnyKey()); 189 | } 190 | } 191 | 192 | if (kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyEnter)) { 193 | while (kb_AnyKey()); 194 | *initialOption = !optionSelected; // Not sure why it had to be ! but who knows 195 | return true; // Something was changed 196 | } 197 | 198 | return false; // Something was not changed 199 | } 200 | 201 | static void menu_FileOpenRedraw(char *fileNames, unsigned int totalLines, unsigned int fileCount, unsigned int fileStartLoc, uint8_t fileSelected, uint8_t rowsPerScreen, uint8_t boxY, uint8_t boxHeight) { 202 | if (!fileCount) { 203 | gfx_SetColor(BACKGROUND); 204 | gfx_FillRectangle_NoClip(86, 117, 138, 18); 205 | 206 | gfx_SetColor(OUTLINE); 207 | gfx_Rectangle_NoClip(84, 115, 142, 22); 208 | gfx_Rectangle_NoClip(85, 116, 140, 20); 209 | 210 | fontlib_SetForegroundColor(TEXT_DEFAULT); 211 | fontlib_SetCursorPosition(106, 120); 212 | fontlib_DrawString("No files found"); 213 | } else { 214 | gfx_SetColor(BACKGROUND); 215 | gfx_FillRectangle_NoClip(86, boxY + 16, 128, boxHeight - 18); 216 | 217 | gfx_SetColor(OUTLINE); 218 | gfx_FillRectangle_NoClip(87 + fileSelected % 2 * 64, boxY + 17 + fileSelected / 2 * 17, 62, 16); // Cursor over selected item in menu 219 | 220 | unsigned int fileNameDrawing = fileStartLoc * 9; 221 | 222 | for (uint8_t drawRow = 0; drawRow < rowsPerScreen; drawRow++) { 223 | fontlib_SetCursorPosition(90, boxY + 19 + drawRow * 17); 224 | fontlib_DrawString(&fileNames[fileNameDrawing]); 225 | fileNameDrawing += 9; 226 | 227 | if (fileNameDrawing == fileCount * 9) { 228 | break; 229 | } 230 | 231 | fontlib_SetCursorPosition(154, boxY + 19 + drawRow * 17); 232 | fontlib_DrawString(&fileNames[fileNameDrawing]); 233 | fileNameDrawing += 9; 234 | } 235 | 236 | ui_DrawScrollbar(216, boxY + 16, boxHeight - 18, totalLines, fileStartLoc / 2, rowsPerScreen); 237 | } 238 | } 239 | 240 | static void menu_FileNew(struct context_t *studioContext) { 241 | while (kb_AnyKey()); 242 | asm_spi_BeginFrame(); 243 | gfx_SetColor(OUTLINE); 244 | gfx_FillRectangle_NoClip(123, 95, 64, 32); 245 | gfx_SetColor(BACKGROUND); 246 | gfx_FillRectangle_NoClip(125, 111, 60, 14); 247 | 248 | fontlib_SetForegroundColor(TEXT_DEFAULT); 249 | fontlib_SetCursorPosition(127, 97); 250 | fontlib_DrawString("New file"); 251 | asm_spi_EndFrame(); 252 | 253 | char *newFile = util_StringInputBox(126, 112, 9, INPUT_UPPERCASE, kb_KeyClear); 254 | 255 | if (newFile != NULL) { 256 | if (asm_files_CheckFileExists(newFile, OS_TYPE_APPVAR)) { 257 | asm_spi_BeginFrame(); 258 | gfx_SetColor(OUTLINE); 259 | gfx_FillRectangle_NoClip(82, 82, 146, 60); 260 | gfx_SetColor(BACKGROUND); 261 | gfx_FillRectangle_NoClip(84, 122, 142, 18); 262 | fontlib_SetForegroundColor(TEXT_DEFAULT); 263 | fontlib_SetCursorPosition(85, 84); 264 | fontlib_DrawString("A file aready exists"); 265 | fontlib_SetCursorPosition(104, 96); 266 | fontlib_DrawString("with that name."); 267 | fontlib_SetCursorPosition(110, 108); 268 | fontlib_DrawString("Overwrite it?"); 269 | asm_spi_EndFrame(); 270 | 271 | if(!menu_YesNo(85, 123, 69)) { 272 | return; // Don't make the file 273 | } 274 | 275 | ti_DeleteVar(newFile, OS_TYPE_APPVAR); 276 | } 277 | 278 | uint8_t file = ti_Open(newFile, "w"); 279 | uint16_t header = 0x7AEF; 280 | ti_Write(&header, 2, 1, file); 281 | ti_Close(file); 282 | 283 | if (!util_OpenFile(studioContext, newFile)) { 284 | struct error_t error = {0, ERROR_NO_MEM}; 285 | menu_Error(error); 286 | } 287 | 288 | //dbg_printf("-----\nfileIsOpen: %d\nfileIsSaved: %d\npageDataStart: %p\nrowDataStart: %p\nfileName: %s\nfileSize: %d\nopenEOF: %p\nnewlineCount: %d\ntotalLines: %d\nnewlineStart: %d\nlineStart: %d\nrow: %d\ncolumn: %d\nrowLength: %d\n-----\n", studioContext->fileIsOpen, studioContext->fileIsSaved, studioContext->pageDataStart, studioContext->rowDataStart, studioContext->fileName, studioContext->fileSize, studioContext->openEOF, studioContext->newlineCount, studioContext->totalLines, studioContext->newlineStart, studioContext->lineStart, studioContext->row, studioContext->column, studioContext->rowLength); 289 | } 290 | } 291 | 292 | static void menu_FileOpen(struct context_t *studioContext, struct preferences_t *studioPreferences, char *fileNames, unsigned int fileCount) { 293 | while (kb_AnyKey()); 294 | 295 | unsigned int rowsPerScreen = (fileCount + 1) / 2; 296 | 297 | if (rowsPerScreen > 8) { 298 | rowsPerScreen = 8; 299 | } 300 | 301 | uint8_t boxY = 101 - rowsPerScreen * 17 / 2; 302 | uint8_t boxHeight = 19 + rowsPerScreen * 17; 303 | 304 | unsigned int fileStartLoc = 0; // It's CEaShell all over again 305 | unsigned int fileSelected = 0; 306 | 307 | asm_spi_BeginFrame(); 308 | gfx_SetColor(OUTLINE); 309 | gfx_Rectangle_NoClip(84, boxY, 142, boxHeight); 310 | gfx_Rectangle_NoClip(85, boxY + 1, 140, boxHeight - 2); 311 | gfx_Rectangle_NoClip(214, boxY + 16, 2, boxHeight - 18); 312 | gfx_FillRectangle_NoClip(86, boxY + 2, 138, 14); 313 | fontlib_SetCursorPosition(122, boxY + 2); 314 | fontlib_DrawString("Open file"); 315 | 316 | menu_FileOpenRedraw(fileNames, (fileCount + 1) / 2, fileCount, 0, 0, rowsPerScreen, boxY, boxHeight); 317 | 318 | asm_spi_EndFrame(); 319 | 320 | bool keyPressed = false; 321 | clock_t clockOffset = clock(); 322 | 323 | unsigned int bottomItem = fileCount + fileCount % 2; 324 | 325 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_Key2nd) && !kb_IsDown(kb_KeyEnter)) { 326 | kb_Scan(); 327 | 328 | if (!kb_AnyKey()) { 329 | keyPressed = false; 330 | clockOffset = clock(); 331 | } 332 | 333 | if (kb_Data[7] && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 16) && fileCount) { 334 | if (kb_IsDown(kb_KeyUp)) { 335 | if (fileSelected < 2) { 336 | if (fileStartLoc) { 337 | fileStartLoc -= 2; 338 | } else { 339 | fileStartLoc = (bottomItem - rowsPerScreen * 2); 340 | fileSelected += rowsPerScreen * 2 - 2; 341 | 342 | if (fileStartLoc + fileSelected + 1 > fileCount) { 343 | fileSelected -= 2; 344 | } 345 | } 346 | } else { 347 | fileSelected -= 2; 348 | } 349 | } else if (kb_IsDown(kb_KeyDown)) { 350 | if (fileSelected + 3 > rowsPerScreen * 2) { 351 | if (fileStartLoc < bottomItem - rowsPerScreen * 2) { 352 | fileStartLoc += 2; 353 | } else if (fileStartLoc + fileSelected + 2 >= fileCount) { 354 | fileStartLoc = 0; 355 | fileSelected -= rowsPerScreen * 2 - 2; 356 | } 357 | } else if (fileStartLoc + fileSelected + 1 == fileCount - 1) { 358 | fileSelected++; 359 | } else { 360 | fileSelected += 2; 361 | } 362 | } 363 | 364 | if (kb_IsDown(kb_KeyLeft) && fileCount > 1) { 365 | if (fileSelected) { 366 | fileSelected--; 367 | } else { 368 | if (fileStartLoc) { 369 | fileSelected = 1; 370 | fileStartLoc -= 2; 371 | } else { 372 | fileStartLoc = bottomItem - rowsPerScreen * 2; 373 | fileSelected = fileCount - fileStartLoc - 1; 374 | } 375 | } 376 | } else if (kb_IsDown(kb_KeyRight) && fileCount > 1) { 377 | if (fileStartLoc + fileSelected + 1 == fileCount && fileSelected == rowsPerScreen * 2 - 2) { 378 | fileSelected--; 379 | } else if (fileSelected + 2 > rowsPerScreen * 2) { 380 | if (fileStartLoc + fileSelected + 1 != fileCount) { 381 | fileStartLoc += 2; 382 | fileSelected = rowsPerScreen * 2 - 2; 383 | } else { 384 | fileStartLoc = 0; 385 | fileSelected = 0; 386 | } 387 | } else { 388 | fileSelected++; 389 | } 390 | } 391 | 392 | asm_spi_BeginFrame(); 393 | menu_FileOpenRedraw(fileNames, (fileCount + 1) / 2, fileCount, fileStartLoc, fileSelected, rowsPerScreen, boxY, boxHeight); 394 | asm_spi_EndFrame(); 395 | 396 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 397 | } 398 | } 399 | 400 | if (kb_IsDown(kb_KeyClear) || !fileCount) { 401 | while (kb_AnyKey()); 402 | return; // Return early 403 | } 404 | 405 | if (!util_OpenFile(studioContext, &fileNames[(fileStartLoc + fileSelected) * 9])) { 406 | gfx_ZeroScreen(); 407 | ui_DrawUIMain(0, studioContext->totalLines, studioContext->lineStart); 408 | 409 | if (studioContext->fileIsOpen) { 410 | ui_UpdateText(studioContext, studioPreferences, UPDATE_ALL); 411 | ui_DrawCursor(studioContext->row, studioContext->column, true, false); 412 | } else { 413 | ui_NoFile(); 414 | } 415 | 416 | ui_DrawMenuBox(0, 168, 73, 55, 1, 3, "New file", "Open file", "Save file"); 417 | struct error_t error = {0, ERROR_NO_MEM}; 418 | menu_Error(error); 419 | } 420 | 421 | //dbg_printf("-----\nfileIsOpen: %d\nfileIsSaved: %d\npageDataStart: %p\nrowDataStart: %p\nfileName: %s\nfileSize: %d\nopenEOF: %p\nnewlineCount: %d\ntotalLines: %d\nnewlineStart: %d\nlineStart: %d\nrow: %d\ncolumn: %d\nrowLength: %d\n-----\n", studioContext->fileIsOpen, studioContext->fileIsSaved, studioContext->pageDataStart, studioContext->rowDataStart, studioContext->fileName, studioContext->fileSize, studioContext->openEOF, studioContext->newlineCount, studioContext->totalLines, studioContext->newlineStart, studioContext->lineStart, studioContext->row, studioContext->column, studioContext->rowLength); 422 | } 423 | 424 | void menu_File(struct context_t *studioContext, struct preferences_t *studioPreferences) { 425 | asm_spi_BeginFrame(); 426 | ui_DrawUIMain(1, studioContext->totalLines, studioContext->lineStart); 427 | asm_spi_EndFrame(); 428 | 429 | while (kb_AnyKey()); // Wait for key to be released 430 | 431 | asm_spi_BeginFrame(); 432 | ui_DrawUIMain(0, studioContext->totalLines, studioContext->lineStart); 433 | ui_DrawMenuBox(0, 168, 73, 55, 0, 3, "New file", "Open file", "Save file"); 434 | asm_spi_EndFrame(); 435 | 436 | uint8_t option = 0; 437 | 438 | bool keyPressed = false; 439 | clock_t clockOffset = clock(); 440 | 441 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_KeyYequ) && !kb_IsDown(kb_Key2nd) && !kb_IsDown(kb_KeyEnter)) { 442 | kb_Scan(); 443 | 444 | if (!kb_AnyKey()) { 445 | keyPressed = false; 446 | clockOffset = clock(); 447 | } 448 | 449 | if (kb_Data[7] && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 16)) { 450 | if (kb_IsDown(kb_KeyUp)) { 451 | if (option == 0) { // Loop the cursor 452 | option = 2; 453 | } else { 454 | option -= 1; 455 | } 456 | } else if (kb_IsDown(kb_KeyDown)) { 457 | if (option == 2) { // Loop the cursor 458 | option = 0; 459 | } else { 460 | option += 1; 461 | } 462 | } 463 | 464 | asm_spi_BeginFrame(); 465 | ui_DrawMenuBox(0, 168, 73, 55, option, 3, "New file", "Open file", "Save file"); 466 | asm_spi_EndFrame(); 467 | 468 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 469 | } 470 | } 471 | 472 | if (kb_IsDown(kb_KeyYequ)) { // Ensure the menu doesn't get opened again immediately 473 | asm_spi_BeginFrame(); 474 | ui_DrawUIMain(1, studioContext->totalLines, studioContext->lineStart); 475 | asm_spi_EndFrame(); 476 | 477 | while (kb_AnyKey()); 478 | } else if (kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyEnter)) { 479 | switch (option) { 480 | case 0: // New file 481 | if (studioContext->fileIsSaved || menu_Warning(WARNING_UNSAVED)) { 482 | edit_RedrawEditor(studioContext, studioPreferences); 483 | menu_FileNew(studioContext); 484 | } 485 | 486 | break; 487 | case 1: { // Open file 488 | if (studioContext->fileIsSaved || menu_Warning(WARNING_UNSAVED)) { 489 | edit_RedrawEditor(studioContext, studioPreferences); 490 | unsigned int fileCount = 0; 491 | util_GetFiles(&fileCount, SOURCE_HEADER); 492 | menu_FileOpen(studioContext, studioPreferences, (char *)os_PixelShadow, fileCount); 493 | } 494 | 495 | break; 496 | } 497 | case 2: // Save file 498 | if (!(studioContext->fileIsOpen)) { 499 | break; 500 | } 501 | 502 | studioContext->fileIsSaved = true; 503 | 504 | if (!asm_files_WriteFile(studioContext->fileName, studioContext->fileSize)) { 505 | struct error_t error = {0, ERROR_NO_MEM}; 506 | menu_Error(error); 507 | } 508 | 509 | while (kb_AnyKey()); 510 | 511 | // dbg_printf("fileIsOpen: %d\nfileIsSaved: %d\npageDataStart: %p\nrowDataStart: %p\nfileName: %s\nfileSize: %d\nopenEOF: %p\nnewlineCount: %d\ntotalLines: %d\nnewLineStart: %d\nlineStart: %d\nrow: %d\ncolumn: %d\nrowLength: %d\ninputMode: %d\n", studioContext->fileIsOpen, studioContext->fileIsSaved, studioContext->pageDataStart, studioContext->rowDataStart, studioContext->fileName, studioContext->fileSize, studioContext->openEOF, studioContext->newlineCount, studioContext->totalLines, studioContext->newlineStart, studioContext->lineStart, studioContext->row, studioContext->column, studioContext->rowLength, studioContext->inputMode); 512 | 513 | break; 514 | default: 515 | break; 516 | } 517 | } 518 | } 519 | 520 | void menu_Goto(struct context_t *studioContext) { 521 | asm_spi_BeginFrame(); 522 | ui_DrawUIMain(3, studioContext->totalLines, studioContext->lineStart); 523 | asm_spi_EndFrame(); 524 | 525 | while (kb_AnyKey()); // Wait for key to be released 526 | 527 | asm_spi_BeginFrame(); 528 | ui_DrawUIMain(0, studioContext->totalLines, studioContext->lineStart); 529 | gfx_SetColor(OUTLINE); 530 | gfx_FillRectangle_NoClip(101, 207, 71, 16); 531 | gfx_Rectangle_NoClip(172, 207, 36, 18); 532 | gfx_Rectangle_NoClip(173, 208, 34, 16); 533 | 534 | gfx_SetColor(BACKGROUND); 535 | gfx_FillRectangle_NoClip(174, 209, 32, 14); 536 | 537 | fontlib_SetForegroundColor(TEXT_DEFAULT); 538 | fontlib_SetCursorPosition(104, 210); 539 | fontlib_DrawString("Goto Line:"); 540 | asm_spi_EndFrame(); 541 | 542 | char *input = util_StringInputBox(175, 210, 5, INPUT_NUMBERS, kb_KeyZoom); 543 | 544 | if (input != NULL) { 545 | int targetLine = asm_misc_StringToInt(input) - 1; 546 | 547 | if (targetLine >= 0 && (unsigned int)targetLine < studioContext->newlineCount) { 548 | studioContext->row = 0; 549 | studioContext->column = 0; 550 | 551 | while (studioContext->newlineStart != (unsigned int)targetLine) { 552 | if ((unsigned int)targetLine < studioContext->newlineStart) { 553 | studioContext->newlineStart -= ((*(studioContext->pageDataStart - 1) == '\n') || !(studioContext->lineStart)); 554 | studioContext->lineStart--; 555 | studioContext->pageDataStart = asm_files_PreviousLine(studioContext->pageDataStart, studioContext->openEOF); 556 | studioContext->rowDataStart = studioContext->pageDataStart; 557 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 558 | } else if (studioContext->lineStart + 14 < studioContext->totalLines) { 559 | studioContext->lineStart++; 560 | studioContext->pageDataStart = asm_files_NextLine(studioContext->pageDataStart); 561 | studioContext->rowDataStart = studioContext->pageDataStart; 562 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 563 | studioContext->newlineStart += (*(studioContext->pageDataStart - 1) == '\n'); 564 | } else { 565 | for (unsigned int currentLine = studioContext->newlineStart; currentLine < (unsigned int)targetLine;) { 566 | studioContext->row++; 567 | studioContext->rowDataStart = asm_files_NextLine(studioContext->rowDataStart); 568 | studioContext->rowLength = asm_files_GetLineLength(studioContext->rowDataStart, studioContext->openEOF); 569 | currentLine += (*(studioContext->rowDataStart - 1) == '\n'); 570 | } 571 | 572 | break; // Ensure it doesn't get stuck in an infinite loop if line is right at the end 573 | } 574 | } 575 | } 576 | } 577 | 578 | if (!kb_IsDown(kb_KeyClear)) { 579 | if (kb_IsDown(kb_KeyZoom)) { // Ensure the menu doesn't get opened again immediately 580 | asm_spi_BeginFrame(); 581 | ui_DrawUIMain(3, studioContext->totalLines, studioContext->lineStart); 582 | asm_spi_EndFrame(); 583 | } 584 | 585 | while (kb_AnyKey()); 586 | } 587 | } 588 | 589 | void menu_CharsRedraw(uint8_t selected, const char *chars) { 590 | gfx_SetColor(OUTLINE); 591 | gfx_FillRectangle_NoClip(165, 178, 117, 45); 592 | gfx_SetColor(BACKGROUND); 593 | gfx_FillRectangle_NoClip(167, 194, 113, 29); 594 | fontlib_SetCursorPosition(167, 180); 595 | fontlib_SetForegroundColor(TEXT_DEFAULT); 596 | fontlib_DrawString("Insert Character"); 597 | gfx_SetColor(OUTLINE); 598 | 599 | unsigned int drawX = 168; 600 | uint8_t drawY = 195; 601 | 602 | for (uint8_t drawing = 0; drawing < 16; drawing++) { 603 | if (selected == drawing) { 604 | gfx_FillRectangle_NoClip(drawX, drawY, 13, 13); 605 | } 606 | 607 | fontlib_SetCursorPosition(drawX + 3, drawY); 608 | fontlib_DrawGlyph(chars[drawing]); 609 | 610 | if (drawing == 7) { 611 | drawX = 168; 612 | drawY = 209; 613 | } else { 614 | drawX += 14; 615 | } 616 | } 617 | } 618 | 619 | char menu_Chars(struct context_t *studioContext) { 620 | asm_spi_BeginFrame(); 621 | ui_DrawUIMain(4, studioContext->totalLines, studioContext->lineStart); 622 | asm_spi_EndFrame(); 623 | 624 | while (kb_AnyKey()); // Wait for key to be released 625 | 626 | bool keyPressed = false; 627 | uint8_t selected = 0; 628 | static const char chars[16] = {'\'', '`', ';', '@', '$', '~', '_', '!', '|', '<', '=', '>', '%', '\\', '#', '&'}; 629 | 630 | asm_spi_BeginFrame(); 631 | menu_CharsRedraw(0, chars); 632 | asm_spi_EndFrame(); 633 | 634 | clock_t clockOffset = clock(); 635 | 636 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_KeyTrace)) { 637 | kb_Scan(); 638 | 639 | if (!kb_AnyKey()) { 640 | keyPressed = false; 641 | clockOffset = clock(); 642 | } 643 | 644 | if (kb_Data[7] && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 16)) { 645 | if (kb_IsDown(kb_KeyUp) || kb_IsDown(kb_KeyDown)) { 646 | if (selected > 7) { 647 | selected -= 8; 648 | } else { 649 | selected += 8; 650 | } 651 | } 652 | 653 | if (kb_IsDown(kb_KeyLeft)) { 654 | if (selected) { 655 | selected--; 656 | } else { 657 | selected = 15; 658 | } 659 | } else if (kb_IsDown(kb_KeyRight)) { 660 | if (selected < 15) { 661 | selected++; 662 | } else { 663 | selected = 0; 664 | } 665 | } 666 | 667 | asm_spi_BeginFrame(); 668 | menu_CharsRedraw(selected, chars); 669 | asm_spi_EndFrame(); 670 | 671 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 672 | } else if (kb_IsDown(kb_Key2nd) || kb_IsDown(kb_KeyEnter)) { 673 | while (kb_AnyKey()); 674 | return chars[selected]; 675 | } 676 | } 677 | 678 | if (!kb_IsDown(kb_KeyClear)) { 679 | if (kb_IsDown(kb_KeyTrace)) { // Ensure the menu doesn't get opened again immediately 680 | asm_spi_BeginFrame(); 681 | ui_DrawUIMain(4, studioContext->totalLines, studioContext->lineStart); 682 | asm_spi_EndFrame(); 683 | } 684 | 685 | while (kb_AnyKey()); 686 | } 687 | 688 | return '\0'; 689 | } 690 | 691 | static void menu_About(void) { 692 | asm_spi_BeginFrame(); 693 | gfx_SetColor(BACKGROUND); 694 | gfx_FillRectangle_NoClip(86, 72, 138, 92); 695 | 696 | gfx_SetColor(OUTLINE); 697 | gfx_Rectangle_NoClip(84, 56, 142, 110); 698 | gfx_Rectangle_NoClip(85, 57, 140, 108); 699 | gfx_FillRectangle_NoClip(86, 58, 138, 14); 700 | 701 | fontlib_SetForegroundColor(TEXT_DEFAULT); 702 | fontlib_SetCursorPosition(138, 58); 703 | fontlib_DrawString("About"); 704 | 705 | fontlib_SetCursorPosition(87, 73); 706 | fontlib_DrawString("eZ80 Studio"); 707 | fontlib_SetCursorPosition(87, 85); 708 | fontlib_DrawString("Version: "VERSION_NO); 709 | 710 | gfx_HorizLine_NoClip(87, 98, 136); 711 | 712 | fontlib_SetCursorPosition(87, 100); 713 | fontlib_DrawString("An eZ80 IDE for TI"); 714 | fontlib_SetCursorPosition(87, 112); 715 | fontlib_DrawString("eZ80 calculators."); 716 | 717 | gfx_HorizLine_NoClip(87, 125, 136); 718 | 719 | fontlib_SetCursorPosition(87, 127); 720 | fontlib_DrawString("\xA9 2022 - 2025"); 721 | fontlib_SetCursorPosition(87, 139); 722 | fontlib_DrawString("RoccoLox Programs,"); 723 | fontlib_SetCursorPosition(87, 151); 724 | fontlib_DrawString("TIny_Hacker."); 725 | 726 | asm_spi_EndFrame(); 727 | 728 | while (kb_AnyKey()); 729 | 730 | while (!kb_AnyKey()); 731 | } 732 | 733 | void menu_Settings(struct context_t *studioContext, struct preferences_t *studioPreferences) { 734 | asm_spi_BeginFrame(); 735 | ui_DrawUIMain(5, studioContext->totalLines, studioContext->lineStart); 736 | asm_spi_EndFrame(); 737 | 738 | while (kb_AnyKey()); // Wait for key to be released 739 | 740 | asm_spi_BeginFrame(); 741 | ui_DrawUIMain(0, studioContext->totalLines, studioContext->lineStart); 742 | ui_DrawMenuBox(203, 168, 107, 55, 0, 3, "Themes \xBB", "Highlighting \xBB", "About"); // \xBB is a right arrow icon 743 | asm_spi_EndFrame(); 744 | 745 | uint8_t option = 0; 746 | 747 | bool keyPressed = false; 748 | clock_t clockOffset = clock(); 749 | 750 | while (!kb_IsDown(kb_KeyClear) && !kb_IsDown(kb_KeyGraph)) { 751 | kb_Scan(); 752 | 753 | if (!kb_AnyKey()) { 754 | keyPressed = false; 755 | clockOffset = clock(); 756 | } 757 | 758 | if ((kb_Data[7] || kb_IsDown(kb_KeyEnter) || kb_IsDown(kb_Key2nd)) && (!keyPressed || clock() - clockOffset > CLOCKS_PER_SEC / 16)) { 759 | if (kb_IsDown(kb_KeyUp)) { 760 | if (option == 0) { // Loop the cursor 761 | option = 2; 762 | } else { 763 | option -= 1; 764 | } 765 | } else if (kb_IsDown(kb_KeyDown)) { 766 | if (option == 2) { // Loop the cursor 767 | option = 0; 768 | } else { 769 | option += 1; 770 | } 771 | } 772 | 773 | if (kb_IsDown(kb_KeyRight) || kb_IsDown(kb_KeyEnter) || kb_IsDown(kb_Key2nd)) { 774 | while (kb_AnyKey()); 775 | 776 | switch (option) { 777 | case 0: // Themes 778 | if (menu_MiniMenu(&(studioPreferences->theme), 158, 151, 47, 40, "Dark", "Light")) { 779 | 780 | if (studioPreferences->theme) { 781 | gfx_SetPalette(darkPalette, sizeof_darkPalette, 0); 782 | } else { 783 | gfx_SetPalette(lightPalette, sizeof_lightPalette, 0); 784 | } 785 | 786 | return; // Exit menu entirely 787 | } 788 | 789 | break; 790 | case 1: // Highlighting 791 | if (menu_MiniMenu(&(studioPreferences->highlighting), 172, 168, 33, 40, "On", "Off")) { 792 | return; 793 | } 794 | 795 | break; 796 | case 2: // About 797 | if (!kb_IsDown(kb_KeyRight)) { // Don't open with the right arrow like the other menus 798 | menu_About(); 799 | return; 800 | } 801 | 802 | break; 803 | } 804 | 805 | edit_RedrawEditor(studioContext, studioPreferences); 806 | } 807 | 808 | asm_spi_BeginFrame(); 809 | ui_DrawMenuBox(203, 168, 107, 55, option, 3, "Themes \xBB", "Highlighting \xBB", "About"); 810 | asm_spi_EndFrame(); 811 | 812 | util_WaitBeforeKeypress(&clockOffset, &keyPressed); 813 | } 814 | } 815 | 816 | if (kb_IsDown(kb_KeyGraph)) { // Ensure the menu doesn't get opened again immediately 817 | asm_spi_BeginFrame(); 818 | ui_DrawUIMain(5, studioContext->totalLines, studioContext->lineStart); 819 | asm_spi_EndFrame(); 820 | 821 | while (kb_AnyKey()); 822 | } 823 | } 824 | --------------------------------------------------------------------------------