diff --git a/.gitignore b/.gitignore index 46f3536..a0f3102 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ -*.swp -build/ -*.3dsx -*.elf -*.smdh +.gitignore +.vscode +build +*.3dsx +*.elf +*.smdh +*.lst \ No newline at end of file diff --git a/Makefile b/Makefile index 93682c0..c7989e6 100644 --- a/Makefile +++ b/Makefile @@ -1,199 +1,199 @@ -#--------------------------------------------------------------------------------- -.SUFFIXES: -#--------------------------------------------------------------------------------- - -ifeq ($(strip $(DEVKITARM)),) -$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") -endif - -TOPDIR ?= $(CURDIR) -include $(DEVKITARM)/3ds_rules - -#--------------------------------------------------------------------------------- -# TARGET is the name of the output -# BUILD is the directory where object files & intermediate files will be placed -# SOURCES is a list of directories containing source code -# DATA is a list of directories containing data files -# INCLUDES is a list of directories containing header files -# -# NO_SMDH: if set to anything, no SMDH file is generated. -# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) -# APP_TITLE is the name of the app stored in the SMDH file (Optional) -# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) -# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) -# ICON is the filename of the icon (.png), relative to the project folder. -# If not set, it attempts to use one of the following (in this order): -# - .png -# - icon.png -# - /default_icon.png -#--------------------------------------------------------------------------------- -TARGET := $(notdir $(CURDIR)) -BUILD := build -SOURCES := source -DATA := data -INCLUDES := include -#ROMFS := romfs -APP_TITLE := Notepad3DS -APP_DESCRIPTION := Text editor for the Nintendo 3DS console -APP_AUTHOR := SomeAmountOfGravy - -#--------------------------------------------------------------------------------- -# options for code generation -#--------------------------------------------------------------------------------- -ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft - -CFLAGS := -g -Wall -O2 -mword-relocations \ - -fomit-frame-pointer -ffunction-sections \ - $(ARCH) - -CFLAGS += $(INCLUDE) -DARM11 -D_3DS - -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 - -ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) - -LIBS := -lctru -lm - -#--------------------------------------------------------------------------------- -# list of directories containing libraries, this must be the top level containing -# include and lib -#--------------------------------------------------------------------------------- -LIBDIRS := $(CTRULIB) - - -#--------------------------------------------------------------------------------- -# no real need to edit anything past this point unless you need to add additional -# rules for different file extensions -#--------------------------------------------------------------------------------- -ifneq ($(BUILD),$(notdir $(CURDIR))) -#--------------------------------------------------------------------------------- - -export OUTPUT := $(CURDIR)/$(TARGET) -export TOPDIR := $(CURDIR) - -export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ - $(foreach dir,$(DATA),$(CURDIR)/$(dir)) - -export DEPSDIR := $(CURDIR)/$(BUILD) - -CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) -SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) -PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) -SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) -BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) - -#--------------------------------------------------------------------------------- -# use CXX for linking C++ projects, CC for standard C -#--------------------------------------------------------------------------------- -ifeq ($(strip $(CPPFILES)),) -#--------------------------------------------------------------------------------- - export LD := $(CC) -#--------------------------------------------------------------------------------- -else -#--------------------------------------------------------------------------------- - export LD := $(CXX) -#--------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------- - -export OFILES := $(addsuffix .o,$(BINFILES)) \ - $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ - $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) - -export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ - $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) - -export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) - -ifeq ($(strip $(ICON)),) - icons := $(wildcard *.png) - ifneq (,$(findstring $(TARGET).png,$(icons))) - export APP_ICON := $(TOPDIR)/$(TARGET).png - else - ifneq (,$(findstring icon.png,$(icons))) - export APP_ICON := $(TOPDIR)/icon.png - endif - endif -else - export APP_ICON := $(TOPDIR)/$(ICON) -endif - -ifeq ($(strip $(NO_SMDH)),) - export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh -endif - -ifneq ($(ROMFS),) - export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) -endif - -.PHONY: $(BUILD) clean all - -#--------------------------------------------------------------------------------- -all: $(BUILD) - -$(BUILD): - @[ -d $@ ] || mkdir -p $@ - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - -#--------------------------------------------------------------------------------- -clean: - @echo clean ... - @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf - - -#--------------------------------------------------------------------------------- -else - -DEPENDS := $(OFILES:.o=.d) - -#--------------------------------------------------------------------------------- -# main targets -#--------------------------------------------------------------------------------- -ifeq ($(strip $(NO_SMDH)),) -$(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh -else -$(OUTPUT).3dsx : $(OUTPUT).elf -endif - -$(OUTPUT).elf : $(OFILES) - -#--------------------------------------------------------------------------------- -# you need a rule like this for each extension you use as binary data -#--------------------------------------------------------------------------------- -%.bin.o : %.bin -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - @$(bin2o) - -#--------------------------------------------------------------------------------- -# rules for assembling GPU shaders -#--------------------------------------------------------------------------------- -define shader-as - $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) - picasso -o $(CURBIN) $1 - bin2s $(CURBIN) | $(AS) -o $@ - echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h - echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h - echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h -endef - -%.shbin.o : %.v.pica %.g.pica - @echo $(notdir $^) - @$(call shader-as,$^) - -%.shbin.o : %.v.pica - @echo $(notdir $<) - @$(call shader-as,$<) - -%.shbin.o : %.shlist - @echo $(notdir $<) - @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) - --include $(DEPENDS) - -#--------------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +#ROMFS := romfs +APP_TITLE := Notepad3DS +APP_DESCRIPTION := Text editor for the Nintendo 3DS console +APP_AUTHOR := SomeAmountOfGravy + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -fomit-frame-pointer -ffunction-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -D__3DS__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +ifneq ($(ROMFS),) + export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(NO_SMDH)),) +$(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh +else +$(OUTPUT).3dsx : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $@ + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h +endef + +%.shbin.o : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.MD b/README.MD index 18a2797..8f2a941 100644 --- a/README.MD +++ b/README.MD @@ -1,27 +1,30 @@ -# Notepad3DS -Text editor for the Nintendo 3DS console. - -## Features -File opening/saving -Search for terms in a file (Will only find the first occurence. Looking to change this in future) -Jump to beginning/end of a file - - -## Usage -Instructions are provided on the bottom screen of the 3DS -The bottom screen is also used to show information about the file and some error messages (file opening failure for example) -Running Notepad3DS through the __Homebrew Launcher__ will mean all file saves/opens will start from the /3ds/ directory. Prefix '../' to your filename to access the root folder. -Running Notepad3DS through a __.cia__ will mean all file saves/opens will start from the root '/' directory. You can access files in other directories from here. For example, 3ds/files/example.txt -### Buttons -__A__ - Select the current line -__B__ - Start a new file (with confirmation) -__X__ - Save current file -__Y__ - Open a new file -__R__ - Search for a term in the file -__DPad/CPad__ - Movement up/down -__L__ - Hold with movement to jump to beginning/end - -__NOTE:__ Notepad3DS can access directories but cannot create them. Saving a file to a non existing directory will __NOT__ save the file correctly. - -## Further information -Each line can hold a maximum of 1024 characters when edited. If a line with more than 1024 characters is edited, it __WILL__ be truncated with no way of undoing this. \ No newline at end of file +# Notepad3DS +Notepad3DS allows you to edit files directly from your 3DS system. + +Originally created in 2017 and updated in 2025 for improved functionality and a modernised backend. + +## What's New? + +**Version 1.2.1 includes:** +- More advanced file editing +- Search results can be cycled +- Fixed a bug where saved files would hold all content in 1 line + +## Coming Soon +- Improved file selection through a visualised hierarchy +- Support for making directories + +## Features +- Line editing, inserting and deleting +- File creation, opening and saving +- File searching +- Display line numbers + +## Usage +The bottom screen displays instructions, file information and any additional information. + +When running Notepad3DS, you will start in the `/3ds` directory. Use `../` to access higher levels in the folder structure. + +> [!NOTE] Notepad3DS can open and save files in existing directories but **cannot create new directories.** Saving a file to a non-existent directory will be **unsuccessful**. + +> [!NOTE] Each line can hold a maximum of 1024 characters when edited. Lines over 1024 characters **WILL** be truncated, with no way of undoing this. \ No newline at end of file diff --git a/source/behaviours.cpp b/source/behaviours.cpp new file mode 100644 index 0000000..1b4173e --- /dev/null +++ b/source/behaviours.cpp @@ -0,0 +1,188 @@ +#include "behaviours.h" + +static char buf[BUFFER_SIZE]; +SwkbdState swkbd; + +// Invokes keyboard, returning button presssed +SwkbdButton get_keyboard(const char* initText, const char* hint) { + buf[0] = '\0'; // Clear buffer + swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 2, -1); + swkbdSetValidation(&swkbd, SWKBD_ANYTHING, SWKBD_ANYTHING, 2); + swkbdSetFeatures(&swkbd, SWKBD_DARKEN_TOP_SCREEN); + swkbdSetInitialText(&swkbd, initText); + swkbdSetHintText(&swkbd, hint); + return swkbdInputText(&swkbd, buf, sizeof(buf)); +} + +// Raises a dialog prompt, halting all other logic until answered. returns user's choice +bool confirm(std::string text) { + print_log(text + " [A] Yes [B] No"); + + while (aptMainLoop()) { + hidScanInput(); + u32 kDown = hidKeysDown(); + + if (kDown & KEY_A) return true; + if (kDown & KEY_B) return false; + + gfxFlushBuffers(); + gfxSwapBuffers(); + gspWaitForVBlank(); + } + return false; +} + +void cycle_matches(File& file, std::vector& results, int match) { + print_instructions(); + unsigned result = results[match]; + + print_log("Showing " + std::to_string(match+1) + " of " + std::to_string(results.size()) + " matches"); + curr_line = result; + if (curr_line > LINES_UNTIL_SCROLL) { + scroll = curr_line - LINES_UNTIL_SCROLL; + } + update_screen(file, curr_line); +} + +// EDIT PAGE +void edit_line(File& file) { + SwkbdButton button = get_keyboard(file.lines[curr_line].c_str(), "Input text here."); + + // Check if user confirmed input (do nothing if user cancelled) + if (button == SWKBD_BUTTON_RIGHT) { + std::string new_text = buf; + + // Add new line if final line, otherwise, just edit + if (curr_line == file.size() -1) + file.add_line(new_text); + else + file.edit_line(new_text, curr_line); + + update_screen(file, curr_line); + } +} + +void delete_line(File& file) { + bool choice = confirm("Delete Line?"); + + // Confirm choice, dont allow the user to delete the final line + if (choice && curr_line != file.size() -1) { + file.delete_line(curr_line); + print_log("Line deleted successfully"); + update_screen(file, curr_line); + } else { + print_log("Did not delete line"); + } +} + +void insert_line(File& file) { + file.insert_line(curr_line); + update_screen(file, curr_line); +} + +std::vector search_file(File& file) { + SwkbdButton button = get_keyboard(NULL, "Input search term here."); // Get term to search for + std::string searchTerm = buf; + std::vector results; + + // Check if user confirmed input (do nothing if user cancelled) + if (button == SWKBD_BUTTON_RIGHT) { + results = file.find(searchTerm); + if (results.empty()) + if (searchTerm.empty()) + print_log("No search term provided"); + else + print_log("Could not find \"" + searchTerm + "\""); + else { + page = SEARCH_PAGE; + curr_match = 0; + cycle_matches(file, results, curr_match); + return results; + } + } + return results; +} + +// FILE PAGE +void save_file(File& file) { + if (file_name.empty()) { + save_as_file(file); + return; + } + + bool choice = confirm("Save file?"); + if (choice) { + bool success = write_file(file_name, file); // Write out characters to file + if (success) + print_log("File saved successfully"); + else + print_log("Failed to save"); + } else + print_log("Save aborted"); + +} + +void new_file(File& file) { + bool choice = confirm("Create new file?"); + + if (choice) { // New file created + curr_line = scroll = 0; + file_name.clear(); + print_current_directory(file_name); + print_log("File created."); + file = File(); + update_screen(file, curr_line); + } else { // New file aborted + print_log("File creation aborted."); + } +} + +void open_file(File& file) { + curr_line = 0; + scroll = 0; + + buf[0] = '\0'; // Clear buffer + SwkbdButton button = get_keyboard(NULL, "Input filename to open."); // Get file name + + // Check if user confirmed input (do nothing if user cancelled) + if (button == SWKBD_BUTTON_RIGHT) { + std::string filename = buf; + + if (filename.empty()) { + print_log("No filename provided."); + } else if (button != SWKBD_BUTTON_NONE) { + File oldfile = file; + file = open_file(filename); + + if (file.read_success) { + file_name = filename; + print_current_directory(file_name); + print_log("Successfully opened " + filename); + update_screen(file, curr_line); + } else { + file = oldfile; + update_screen(file, curr_line); + print_log("Failed to open " + filename); + } + } + } +} + +void save_as_file(File& file) { + SwkbdButton button = get_keyboard(NULL, "Save as filename"); // Get file name + // Check if user confirmed input (do nothing if user cancelled) + if (button == SWKBD_BUTTON_RIGHT) { + std::string filename = buf; + if (filename.empty()) { + print_log("No filename provided."); + } else if (button != SWKBD_BUTTON_NONE) { + bool success = write_file(filename, file); // Write out characters to file + if (success) { + file_name = filename; + print_current_directory(file_name); + print_log("File written to " + filename); + } else + print_log("Failed to write " + filename); + } + } +} \ No newline at end of file diff --git a/source/behaviours.h b/source/behaviours.h new file mode 100644 index 0000000..a653580 --- /dev/null +++ b/source/behaviours.h @@ -0,0 +1,27 @@ +#pragma once +#include <3ds.h> +#include + +#include "display.h" +#include "file_io.h" + +#define BUFFER_SIZE 1025 // Notepad's line limit + 1 (for null character \0) +#define LINES_UNTIL_SCROLL 28 // Begin scrolling past this line + +extern int curr_line; +extern unsigned curr_match; + +// SEARCH PAGE COMMAND +void cycle_matches(File& file, std::vector& results, int match); + +// EDIT PAGE COMMANDS +void edit_line(File& file); +void delete_line(File& file); +void insert_line(File& file); +std::vector search_file(File& file); + +// FILE PAGE COMMANDS +void save_file(File& file); +void new_file(File& file); +void open_file(File& file); +void save_as_file(File& file); \ No newline at end of file diff --git a/source/display.cpp b/source/display.cpp index a39eafc..22320a7 100644 --- a/source/display.cpp +++ b/source/display.cpp @@ -1,168 +1,110 @@ -#include "display.h" -#include -#include -#include -#include -#include -void clear_screen() { - consoleSelect(&topScreen); - //Cursor to top left - std::cout << SCREEN_START_POINT; - //Clear screen with empty lines - for (int i = 0; i < CLEAR_SCREEN_LINES; i++) - std::cout << std::string(70, ' '); - std::cout << SCREEN_START_POINT; -} - -void clear_save_status() { - consoleSelect(&bottomScreen); - printf(SAVE_STATUS_LINE); - printf(" "); - printf(SAVE_STATUS_LINE); -} - -void print_version(std::string version) { - consoleSelect(&bottomScreen); - printf(VERSION_LINE); - std::cout << version << std::endl; -} - -void print_instructions() { - consoleSelect(&bottomScreen); - printf(INSTRUCTION_LINE); - printf("Press A to select current line\n"); - printf("Press B to create a new file\n"); - printf("Press X to save file\n"); - printf("Press Y to open file\n"); - printf("Press R to search file\n"); - printf("Hold L to jump to top/end with up/down\n"); - printf("Press DPad or CPad to move up/down\n"); - printf("Press START to exit\n"); -} - - -std::string char_vec_to_string(std::vector& line) { - - std::string temp_str = ""; - int letters = 0; - for (const auto& ch : line) { - if (letters != MAX_WIDTH) { - //Store characters to display - temp_str.push_back(ch); - letters++; - } else { - //Too much text, display new line - temp_str.push_back('\n'); - break; - } - } - return temp_str; -} - -void print_text(const char* str, unsigned int count, unsigned int selected_line) { - - if (count == selected_line) - if (str[0] == '\n') { - printf(SELECTED_TEXT_COLOUR); - printf("(empty line)"); - printf(DEFAULT_TEXT_COLOUR); - printf("\n"); - } else { - printf(SELECTED_TEXT_COLOUR); - printf("%s", str); - printf(DEFAULT_TEXT_COLOUR); - } - else { - printf(DEFAULT_TEXT_COLOUR); - printf("%s", str); - } -} - -void print_save_status(std::string message) { - clear_save_status(); - std::cout << message << std::endl; -} - -void clear_line_status() { - consoleSelect(&bottomScreen); - printf(LINE_STATUS_LINE); - printf(" "); - printf(LINE_STATUS_LINE); -} - -void print_line_status(unsigned int current_line) { - clear_line_status(); - std::cout << "Current line: " << current_line+1; -} - -void clear_directory_status() { - consoleSelect(&bottomScreen); - printf(DIRECTORY_LINE); - printf(" "); - printf(DIRECTORY_LINE); -} - -void print_directory_status(std::string filename) { - clear_directory_status(); - std::cout << "Current directory: " << filename; - -} - -void update_screen(File& file, unsigned int current_line) { - clear_screen(); - consoleSelect(&bottomScreen); - print_line_status(current_line); - consoleSelect(&topScreen); - unsigned int count = 0; - - //No scrolling needed - if (file.lines.size() - 1 <= MAX_LINES) { - for (auto iter = file.lines.begin(); iter != file.lines.end(); iter++) { - //Print everything in the vector that iterator points to - std::string temp = char_vec_to_string(*iter); - const char* str_to_print = temp.c_str(); - print_text(str_to_print, count, current_line); - count++; - } - - //Scrolling needed - } else { - - auto iter = file.lines.begin(); - - if (current_line > 1 ) { - advance(iter, (current_line -1)); - } - else { - advance(iter, current_line); - } - - if (scroll == 0) { - for (int line = 0; line <= MAX_LINES; line++) { - iter = file.lines.begin(); - advance(iter, line); - - std::string temp = char_vec_to_string(*iter); - const char* str_to_print = temp.c_str(); - print_text(str_to_print, count, current_line); - count++; - } - - } else { - for (int line = scroll; line <= MAX_LINES + scroll; line++) { - - - iter = file.lines.begin(); - advance(iter, line); - std::string temp = char_vec_to_string(*iter); - const char* str_to_print = temp.c_str(); - print_text(str_to_print, count, current_line-scroll); - count++; - - } - } - - } - -} - +#include "display.h" + +void clear_screen() { + consoleClear(); + std::cout << SCREEN_START_POINT; // Cursor to top left +} + +void clear_line(const std::string line) { + std::cout << line << FULL_CLEAR; +} + +void print_version(std::string version) { + std::cout << SELECTED_TEXT_COLOUR; + clear_line(VERSION_LINE); + std::cout << version << DEFAULT_TEXT_COLOUR; +} + +void print_current_line(int current_line) { + clear_line(CURRENT_LINE_LINE); + std::cout << "Line: " << current_line+1; +} + +void print_current_directory(std::string filename) { + consoleSelect(&bottomScreen); + clear_line(CURRENT_DIRECTORY_LINE); + std::string name = !filename.empty() ? filename : "(nothing)"; + std::cout << "Current file: " << name; +} + +void print_log(std::string message) { + consoleSelect(&bottomScreen); + clear_line(LOG_LINE); + std::cout << message; +} + +void print_instructions() { + consoleSelect(&bottomScreen); + std::cout << INSTRUCTION_LINE; + for (int i = 0; i < 12; i++) { // 14 == amount of lines that make up the instructions + std::cout << FULL_CLEAR << '\n'; + } + if (page == SEARCH_PAGE) { + std::cout << + INSTRUCTION_LINE << + "[Search Mode]\n\n" << + "B: Exit Search Mode\n\n" << + "R: Next Match\n" << + "L: Previous Match\n"; + return; + } + std::cout << + INSTRUCTION_LINE << + (page == EDIT_PAGE ? "[Edit Mode]\n\n" : "[File Mode]\n\n") << + "DPad/CPad: move up/down\n\n"; + + // Face buttons + if (page == EDIT_PAGE) { + std::cout << + "A: Edit Line\n" << + "B: Delete Line\n" << + "X: Insert Line Above\n" << + "Y: Search\n\n"; + } + else { + std::cout << + "A: Open File\n" << + "B: New File\n" << + "X: Save\n" << + "Y: Save As\n\n"; + } + std::cout << + "R: Switch Editing Mode\n" << + "L: Jump to Start/End\n\n" << + "SELECT: Toggle Line Numbers\n" << + "START: Exit Notepad3DS\n"; +} + +void print_text(std::string str, int count, int current_line) { + + int visible_digits = std::to_string(count + scroll).length(); + unsigned int width = (show_line_numbers ? MAX_WIDTH - visible_digits - 1 : MAX_WIDTH); // Offset max line length by space taken by line numbers + + if (str.size() > width) { str = str.substr(0, width - 3) + "..."; } // Crop line if too long + + // Set colour based on selected + std::cout << (count == current_line ? SELECTED_TEXT_COLOUR : DEFAULT_TEXT_COLOUR); + + // Print the line number + if (show_line_numbers) { std::cout << count + scroll + 1 << "|"; } + + if (str.empty()) { // If line is empty, print a placeholder + if (count == current_line) std::cout << "(empty line)" << DEFAULT_TEXT_COLOUR; + } + else std::cout << str << DEFAULT_TEXT_COLOUR; // Print line and reset to default colour + + if (count < MAX_LINES -1) { std::cout << '\n'; } // New line if not the end, otherwise skip to display more lines on-screen +} + +void update_screen(File& file, int current_line) { + consoleSelect(&bottomScreen); + print_current_line(current_line); + + consoleSelect(&topScreen); + clear_screen(); + auto iter = file.lines.begin() + scroll; + for (int line = 0; iter != file.lines.end() && line < MAX_LINES; line++) { + print_text(*iter, line, current_line - scroll); + iter++; + } +} \ No newline at end of file diff --git a/source/display.h b/source/display.h index 9a635ce..0944f8f 100644 --- a/source/display.h +++ b/source/display.h @@ -1,44 +1,49 @@ -#pragma once - -#include "file.h" -#include <3ds.h> -#include - -#define CLEAR_SCREEN_LINES 40 -#define MAX_LINES 28 -#define MAX_WIDTH 49 -#define SCREEN_START_POINT "\x1b[0;0H" -#define LINE_STATUS_LINE "\x1b[12;0H" -#define SAVE_STATUS_LINE "\x1b[14;0H" -#define INSTRUCTION_LINE "\x1b[0;0H" -#define VERSION_LINE "\x1b[11;0H" -#define DIRECTORY_LINE "\x1b[17;0H" - -#define DEFAULT_TEXT_COLOUR "\x1b[0m" -#define SELECTED_TEXT_COLOUR "\x1b[47;30m" - -extern PrintConsole topScreen, bottomScreen; -extern int scroll; - -//Clears everything off the top screen -void clear_screen(); - -//Updates current selected screen with contents of file -void update_screen(File& file, unsigned int current_line); - -void print_text(const char* str, unsigned int count, unsigned int selected_line); - -void clear_save_status(); -void print_save_status(std::string message); - -void clear_line_status(); -void print_line_status(unsigned int current_line); - -void clear_directory_status(); -void print_directory_status(std::string filename); - -void print_instructions(); - -void print_version(std::string version); - -std::string char_vec_to_string(std::vector& line); +#pragma once +#include <3ds.h> +#include + +#include "file.h" + +#define MAX_LINES 30 // 30 Lines down fit on screen +#define MAX_WIDTH 50 // 50 Characters across fit on top screen (40 on bottom screen) + +#define SCREEN_START_POINT "\x1b[0;0H" // Top Left + +#define VERSION_LINE "\x1b[1;0H" +#define CURRENT_DIRECTORY_LINE "\x1b[2;0H" +#define CURRENT_LINE_LINE "\x1b[3;0H" + +#define LOG_LINE "\x1b[7;0H" +#define INSTRUCTION_LINE "\x1b[11;0H" + +#define DEFAULT_TEXT_COLOUR "\x1b[0m" // Black BG +#define SELECTED_TEXT_COLOUR "\x1b[47;30m" // White BG + +#define FULL_CLEAR "\x1b[2K" // Clears entire line + +enum Pages { + EDIT_PAGE, + FILE_PAGE, + SEARCH_PAGE +}; + +extern PrintConsole topScreen, bottomScreen; +extern int scroll; +extern bool show_line_numbers; +extern std::string file_name; +extern Pages page; + +void clear_screen(); // Clear entire screen + +void clear_line(std::string line); // Clear singular line + +void print_version(std::string version); +void print_current_line(std::string current_line); +void print_current_directory(std::string filename); + +void print_log(std::string message); + +void print_instructions(); + +void print_text(std::string str, int count, int current_line); // Called MAX_LINES times in update_screen, drawing each line +void update_screen(File& file, int current_line); // Update screen with contents of file \ No newline at end of file diff --git a/source/file.cpp b/source/file.cpp index ac94a62..4222347 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -1,49 +1,38 @@ -#include "file.h" -#include - -#include -void File::add_line(std::vector& new_text) { - //Add a newline as 3ds keyboard doesn't - new_text.push_back('\n'); - //Add line to end - lines.push_back(new_text); -} - -void File::edit_line(std::vector& new_text, unsigned int line) { - //Add a newline as 3ds keyboard doesn't - new_text.push_back('\n'); - auto line_iter = lines.begin(); - if (line > 0) - advance(line_iter, line); - //Delete current line - line_iter = lines.erase(line_iter); - //Insert new_text - lines.insert(line_iter, new_text); - - -} - -int File::find(const char* search_term) { - int line_number = 0; - if (search_term[0] == '\0') - return -1; - for(auto line : this->lines) { - auto it = std::search(line.begin(), line.end(), search_term, search_term + strlen(search_term)); - if (it != line.end()) - return line_number; - line_number++; - - } - - return -1; - -} - -std::vector char_arr_to_vector(char* arr) { - std::vector text; - while(*arr != '\0') { - text.push_back(*arr); - arr++; - } - return text; -} +#include "file.h" + +void File::add_line(const std::string& new_text) { + lines.back() = new_text; + lines.push_back(""); +} + +void File::edit_line(std::string& new_text, int& line) { + lines[line] = new_text; +} + +void File::insert_line(int& line) { + lines.insert(lines.begin() + line, ""); +} + +// Eventually i want to add support for deleting lines +void File::delete_line(int& line) { + auto line_iter = lines.begin() + line; + + line_iter = lines.erase(line_iter); // Delete line +} + +std::vector File::find(std::string search_term) { + + std::vector results; + + int line_number = 0; + if (search_term.empty()) + return results; // Early return if search term is null + + for(auto& line : this->lines) { + size_t pos = line.find(search_term); + if (pos != std::string::npos) + results.push_back(line_number); // Add the line number to the results vector + line_number++; + } + return results; +} \ No newline at end of file diff --git a/source/file.h b/source/file.h index 099883e..b19af97 100644 --- a/source/file.h +++ b/source/file.h @@ -1,30 +1,26 @@ -#pragma once -#include -#include -//File struct to store lines in a file -//Uses a list of vector to store lines. -//vector stores text. -struct File { - - File() { - lines.push_back(std::vector{'\n'}); - } - - //Called when an empty line is selected - //vector will be filled with a c-style string - void add_line(std::vector& new_text); - - //Called when an existing line is selected - //line is current selected line. Used to advance list iterator - void edit_line(std::vector& new_text, unsigned int line); - - int find(const char* search_term); - - int size() {return lines.size();} - - std::list> lines; - //Used to check if file open was successful - bool read_success = false; -}; - -std::vector char_arr_to_vector(char* arr); +#pragma once +#include +#include + +// File struct to store lines in a file +// Uses a vector of strings to store lines. +struct File { + + std::vector lines; + bool read_success = false; // Check if file open was successful + + File() { lines.push_back(""); } + + void add_line(const std::string& new_text); + + void edit_line(std::string& new_text, int& line); + + void insert_line(int& line); + + void delete_line(int& line); + + std::vector find(std::string search_term); + + int size() { return lines.size(); } + +}; \ No newline at end of file diff --git a/source/file_io.cpp b/source/file_io.cpp index fe04e2e..79e7f37 100644 --- a/source/file_io.cpp +++ b/source/file_io.cpp @@ -1,40 +1,29 @@ -#include -#include #include "file_io.h" -bool write_to_file(std::string& filename, File& file) { + +bool write_file(std::string& filename, File& file) { std::ofstream new_file(filename); - for (auto iter = file.lines.begin(); iter != file.lines.end(); iter++) { - for (const auto& ch: *iter) - new_file << ch; + for (std::string line : file.lines) { + new_file << line; + if (line != file.lines.back()) { new_file << '\n'; } // Dont create new line if its the end of the file } + new_file.close(); - if (new_file.bad()) - return false; - return true; + return !new_file.bad(); } File open_file(std::string& filename) { - std::ifstream file_open (filename); + std::ifstream file_open(filename); File file; - //Remove newline at start - file.lines.pop_front(); + file.read_success = false; + if (file_open.is_open()) { std::string line; - while (getline(file_open, line)) { - std::vector line_vec; - for (const auto& ch : line) - line_vec.push_back(ch); - file.add_line(line_vec); - } - //If the last line in the file doesn't begin with a newline - if (*file.lines.back().begin() != '\n') { - file.lines.push_back(std::vector {'\n'}); + while (std::getline(file_open, line)) { + file.add_line(line); } file.read_success = true; file_open.close(); - } else { - file.read_success = false; } return file; -} +} \ No newline at end of file diff --git a/source/file_io.h b/source/file_io.h index ddc8836..7c64dbb 100644 --- a/source/file_io.h +++ b/source/file_io.h @@ -1,11 +1,12 @@ #pragma once -#include +#include + #include "file.h" -//Attempts to write contents of file to filename. -//Returns false if failed -bool write_to_file(std::string& filename, File& file); -//Attempts to open filename -//Returns a new File -//Will overwrite current File on failure OR success -File open_file(std::string& filename); +// Attempts to write contents of file to filename. +// Returns false if failed to write +bool write_file(std::string& filename, File& file); + +// Attempts to open filename +// Returns the opened File +File open_file(std::string& filename); \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index 3a02ed3..097e59c 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,283 +1,148 @@ -#include <3ds.h> -#include -#include -#include -#include -#include "file.h" -#include "display.h" -#include "file_io.h" - -#define BUFFER_SIZE 1025 //Notepad's line limit + \0 -#define MAX_BOTTOM_SIZE 28 - -#define VERSION "Notepad3DS Version 1.1.2" - - -PrintConsole topScreen, bottomScreen; -int scroll = 0; -bool fast_scroll = false; - -void move_down(File file); -void move_up(File file); - -unsigned int curr_line = 0; - -int main(int argc, char **argv) -{ - gfxInitDefault(); - consoleInit(GFX_TOP, &topScreen); - consoleInit(GFX_BOTTOM, &bottomScreen); - consoleSelect(&bottomScreen); - //Software keyboard thanks to fincs - print_instructions(); - - print_version(VERSION); - - File file; //Use as default file - - update_screen(file, curr_line); - - while (aptMainLoop()) - { - - hidScanInput(); - - u32 kDown = hidKeysDown(); - u32 kHeld = hidKeysHeld(); - - if (kDown & KEY_START) - break; - - static SwkbdState swkbd; - static char mybuf[BUFFER_SIZE]; - SwkbdButton button = SWKBD_BUTTON_NONE; - bool didit = false; - - swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 1, -1); - swkbdSetValidation(&swkbd, SWKBD_ANYTHING, SWKBD_ANYTHING, 2); - swkbdSetFeatures(&swkbd, SWKBD_DARKEN_TOP_SCREEN); - - if (kDown & KEY_A) { - //Select current line for editing - swkbdSetHintText(&swkbd, "Input text here."); - //Iterator to find current selected line - auto line = file.lines.begin(); - if (curr_line < file.lines.size()) - { - if (curr_line != 0) - advance(line, curr_line); - - if (curr_line == file.lines.size() - 1) { - file.lines.push_back(std::vector{'\n'}); - } - //Need a char array to output to keyboard - char current_text[BUFFER_SIZE] = ""; - copy(line->begin(), line->end(), current_text); - swkbdSetInitialText(&swkbd, current_text); - } - didit = true; - button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); - } - - if (kDown & KEY_B) { - //Create new file - - //Clear buffer - memset(mybuf, '\0', BUFFER_SIZE); - //Confirm creating a new file - swkbdSetHintText(&swkbd, "Are you sure you want to open a BLANK file? y/n"); - button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); - if (mybuf[0] == 'y') { - File blankFile; - file = blankFile; - curr_line = 0; - scroll = 0; - update_screen(file, curr_line); - print_save_status("New file created"); - } else - print_save_status("No new file created"); - } - - if (kDown & KEY_R) { - //find a thing - - //Clear buffer - memset(mybuf, '\0', BUFFER_SIZE); - //Get term to search for - swkbdSetHintText(&swkbd, "Input search term here."); - button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); - int line = file.find(mybuf); - if (line < 0) - printf("Could not find %s", mybuf); - else { - printf("Found %s at %d", mybuf, line); - curr_line = line; - if (curr_line > MAX_BOTTOM_SIZE) { - scroll = curr_line - MAX_BOTTOM_SIZE; - } - update_screen(file, curr_line); - } - - } - - if (kHeld & KEY_L) { - //If held, allows for jumping to end and start of file - fast_scroll = true; - } else { - fast_scroll = false; - } - - if (kDown & KEY_X) { - //Save current file - //Clear buffer - memset(mybuf, '\0', BUFFER_SIZE); - - //Get file name - - swkbdSetHintText(&swkbd, "Input filename here."); - button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); - std::string filename = ""; - for (int i = 0; mybuf[i] != '\0'; i++) - filename.push_back(mybuf[i]); - - //Write out characters to file - bool success = write_to_file(filename, file); - - if (success) { - print_save_status("File written to " + filename); - print_directory_status(filename); - } else { - print_save_status("Failed to write " + filename); - } - - } - - if (kDown & KEY_Y) { - //Similar code to pressing X, see about refactoring - //Open a file - curr_line = 0; - scroll = 0; - //Clear buffer - memset(mybuf, '\0', BUFFER_SIZE); - - //Get file name - - swkbdSetHintText(&swkbd, "Input filename here."); - button = swkbdInputText(&swkbd, mybuf, sizeof(mybuf)); - std::string filename = ""; - for (int i = 0; mybuf[i] != '\0'; i++) - filename.push_back(mybuf[i]); - File oldfile = file; - file = open_file(filename); - - //print functions here seem to crash the program - if (file.read_success) { - update_screen(file, curr_line); - clear_save_status(); - std::cout << "Successfully opened " << filename << std::endl; - clear_directory_status(); - std::cout << "Current file: " << filename; - //print_directory_status(filename); - consoleSelect(&topScreen); - //print_save_status("Successfully opened " + filename); - } else { - file = oldfile; - update_screen(file, curr_line); - clear_save_status(); - std::cout << "Failed to open " << filename << std::endl; - consoleSelect(&topScreen); - //print_save_status("Failed to open " + filename); - } - } - - if (kDown & KEY_DDOWN) { - //Move a line down (towards bottom of screen) - move_down(file); - } - - if (kHeld & KEY_CPAD_DOWN) { - //Move a line down (towards bottom of screen) - //as long as down is held - move_down(file); - } - if (kDown & KEY_DUP) { - //Move a line up (towards top of screen) - move_up(file); - } - - - if (kHeld & KEY_CPAD_UP) { - //Move a line up (towards top of screen) - //as long as up is held - move_up(file); - } - - - if (didit) - { - if (button != SWKBD_BUTTON_NONE) - { - std::vector new_text = char_arr_to_vector(mybuf); - - if (curr_line >= file.lines.size()) { - //Empty line, add a new one. - file.add_line(new_text); - } else { - file.edit_line(new_text, curr_line); - } - update_screen(file, curr_line); - } else - printf("swkbd event: %d\n", swkbdGetResult(&swkbd)); - } - - // Flush and swap framebuffers - gfxFlushBuffers(); - gfxSwapBuffers(); - - gspWaitForVBlank(); - } - - gfxExit(); - return 0; -} - -void move_down(File file) { - //Move a line down (towards bottom of screen) - if (curr_line < file.lines.size() - 1) { - if (fast_scroll) { - curr_line = file.lines.size()-1; - scroll = curr_line - MAX_BOTTOM_SIZE; - - } else { - - if ( (curr_line - scroll >= MAX_BOTTOM_SIZE) && (curr_line < file.lines.size() ) ) { - scroll++; - curr_line++; - } else { - curr_line++; - } - } - update_screen(file, curr_line); - } - -} - -void move_up(File file) { - //Move a line up (towards top of screen) - - if (curr_line != 0) { - if (fast_scroll) { - //Jump to the top - curr_line = 0; - scroll = 0; - - } else { - - curr_line--; - if (curr_line - scroll <= 0 && scroll != 0) { - scroll--; - } - } - update_screen(file, curr_line); - } -} +#include <3ds.h> + +#include "behaviours.h" +#include "display.h" +#include "file_io.h" +#include "file.h" + +#define VERSION "Notepad3DS Version 1.2.1" + +PrintConsole topScreen, bottomScreen; + +int scroll = 0; +int curr_line = 0; +bool fast_scroll = false; +bool show_line_numbers = false; +std::string file_name; +unsigned curr_match = 0; +std::vector searchResults; +Pages page = EDIT_PAGE; + +void move_down(File& file); +void move_up(File& file); + +int main(int argc, char **argv) +{ + gfxInitDefault(); + + consoleInit(GFX_TOP, &topScreen); + consoleInit(GFX_BOTTOM, &bottomScreen); + consoleSelect(&bottomScreen); + + print_version(VERSION); + print_current_directory(file_name); + print_instructions(); + + File file; // Default file + + update_screen(file, curr_line); + while (aptMainLoop()) + { + hidScanInput(); + u32 kDown = hidKeysDown(); + u32 kHeld = hidKeysHeld(); + + // want to add visual hierarchy eventually, example: + + // this + // |—— is + // |—— a + // `—— nested + // |—— structure + // `—— example + + // Terminate program + if (kDown & KEY_START) break; + + // Toggle line numbers + if (kDown & KEY_SELECT) { + show_line_numbers = !show_line_numbers; + update_screen(file, curr_line); + } + + // Buttons do different actions depending on current page + switch (page) { + case EDIT_PAGE: + if ((kDown & KEY_DDOWN) || (kHeld & KEY_CPAD_DOWN)) move_down(file); + if ((kDown & KEY_DUP) || (kHeld & KEY_CPAD_UP)) move_up(file); + fast_scroll = kHeld & KEY_L; // If held, allows for jumping to end and start of file + + if (kDown & KEY_A) edit_line(file); + if (kDown & KEY_B) delete_line(file); + if (kDown & KEY_X) insert_line(file); + if (kDown & KEY_Y) searchResults = search_file(file); + if (kDown & KEY_R) { page = FILE_PAGE; print_instructions(); } + break; + + case FILE_PAGE: + if ((kDown & KEY_DDOWN) || (kHeld & KEY_CPAD_DOWN)) move_down(file); + if ((kDown & KEY_DUP) || (kHeld & KEY_CPAD_UP)) move_up(file); + fast_scroll = kHeld & KEY_L; // If held, allows for jumping to end and start of file + + if (kDown & KEY_A) open_file(file); + if (kDown & KEY_B) new_file(file); + if (kDown & KEY_X) save_file(file); + if (kDown & KEY_Y) save_as_file(file); + if (kDown & KEY_R) { page = EDIT_PAGE; print_instructions(); } + break; + + case SEARCH_PAGE: + if (kDown & KEY_B) { + page = EDIT_PAGE; + print_instructions(); + print_log(""); + } + if (kDown & KEY_L && curr_match > 0) cycle_matches(file, searchResults, --curr_match); + if (kDown & KEY_R && curr_match < searchResults.size()-1) cycle_matches(file, searchResults, ++curr_match); + break; + } + + // Flush and swap framebuffers + gfxFlushBuffers(); + gfxSwapBuffers(); + gspWaitForVBlank(); + } + gfxExit(); + return 0; +} + +//Move a line down (towards bottom of screen) +void move_down(File& file) { + if (file.size() == 0) return; + if (curr_line < file.size() - 1) { // Check if already at bottom + + // Jump if holding L + if (fast_scroll) { + curr_line = file.size()-1; + scroll = curr_line - LINES_UNTIL_SCROLL; + } + // Normal scroll + else { + if ( (curr_line - scroll >= LINES_UNTIL_SCROLL) && (curr_line < file.size() ) ) { + scroll++; + } + curr_line++; + } + update_screen(file, curr_line); + } +} + +//Move a line up (towards top of screen) +void move_up(File& file) { + if (curr_line != 0) { // Check if already at top + + //Jump if holding L + if (fast_scroll) { + curr_line = 0; + scroll = 0; + } + // Normal scroll + else { + curr_line--; + if (curr_line - scroll <= 0 && scroll != 0) { + scroll--; + } + } + update_screen(file, curr_line); + } +} \ No newline at end of file