diff --git a/Makefile.arm9 b/Makefile.arm9 index 9e86291..7a9b342 100644 --- a/Makefile.arm9 +++ b/Makefile.arm9 @@ -13,10 +13,10 @@ ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/ SOURCEDIRS := arm9/source INCLUDEDIRS := -GFXDIRS := +GFXDIRS := arm9/graphics BINDIRS := arm9/data AUDIODIRS := -FONTSDIRS := arm9/fonts +FONTSDIRS := BITMAPSDIRS := arm9/bitmaps TOOLSDIR := tools @@ -90,10 +90,6 @@ ifneq ($(GFXDIRS),) SOURCES_PNG := $(shell find -L $(GFXDIRS) -name "*.png") INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(GFXDIRS)) endif -ifneq ($(FONTSDIRS),) - SOURCES_FONTS := $(shell find -L $(FONTSDIRS) -name "*.png") - INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(FONTSDIRS)) -endif ifneq ($(BITMAPSDIRS),) SOURCES_BITMAPS := $(shell find -L $(BITMAPSDIRS) -name "*.png") INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BITMAPSDIRS)) @@ -231,13 +227,6 @@ $(BUILDDIR)/%.png.o $(BUILDDIR)/%.h : %.png %.grit $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.c $(V)touch $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.h -$(BUILDDIR)/arm9/fonts/font_3x5.raw.o : arm9/fonts/font_3x5.png - @echo $(notdir $<) - @$(MKDIR) -p $(@D) - $(V)python3 $(TOOLSDIR)/font_3x5_pack.py $< $(basename $(basename $@)).raw - $(V)$(BLOCKSDS)/tools/bin2c/bin2c $(basename $(basename $@)).raw $(@D) - $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(basename $(basename $@)).raw.o $(basename $(basename $@))_raw.c - $(BUILDDIR)/%.raw.o : %.png @echo $(notdir $<) @$(MKDIR) -p $(@D) diff --git a/arm9/fonts/font_3x5.png b/arm9/fonts/font_3x5.png deleted file mode 100644 index 34827f1..0000000 Binary files a/arm9/fonts/font_3x5.png and /dev/null differ diff --git a/arm9/graphics/effectinput.grit b/arm9/graphics/effectinput.grit new file mode 100644 index 0000000..e3d53d8 --- /dev/null +++ b/arm9/graphics/effectinput.grit @@ -0,0 +1 @@ +-gt -gB4 -m -p! -ftc diff --git a/arm9/graphics/effectinput.png b/arm9/graphics/effectinput.png new file mode 100644 index 0000000..14c797f Binary files /dev/null and b/arm9/graphics/effectinput.png differ diff --git a/arm9/source/main.cpp b/arm9/source/main.cpp index 917f50f..5dd3d37 100644 --- a/arm9/source/main.cpp +++ b/arm9/source/main.cpp @@ -25,7 +25,7 @@ // #define SHOW_ALL_SETTINGS #define GURU // Show guru meditations #define USE_FAT -// #define ENABLE_EFFECT_MENU +#define ENABLE_EFFECT_MENU #include #include @@ -49,6 +49,8 @@ #include "tobkit/patternview.h" #include "tobkit/normalizebox.h" #include "tobkit/themeselectorbox.h" +#include "tobkit/fxkeyboard.h" +#include "tobkit/digitbox.h" using namespace tobkit; #include @@ -141,14 +143,15 @@ u16 mykey_LEFT = KEY_LEFT, mykey_UP = KEY_UP, mykey_RIGHT = KEY_RIGHT, mykey_DOW GUI *gui; // - Button *buttonrenameinst, *buttonrenamesample, *buttontest, *buttonstopnote, *buttonemptynote, *buttondelnote, *buttoninsnote2, - *buttondelnote2, *buttoninsnote; + Button *buttonrenameinst, *buttonrenamesample, *buttontest, *buttonstopnote, *buttoncpprm, *buttonemptynote, *buttonemptyfx, *buttondelnote, *buttoninsnote2, + *buttondelnote2, *buttoninsnote, *buttonlerpfx; BitButton *buttonswitchsub, *buttonplay, *buttonstop, *buttonpause; CheckBox *cbscrolllock; ToggleButton *tbrecord, *tbmultisample; - Label *labeladd, *labeloct; - NumberBox *numberboxadd, *numberboxoctave; + Label *labeladd, *labeloct, *labelfxcat, *labelfxop, *labeleffectpar; + NumberBox *numberboxadd, *numberboxoctave, *numberboxfxcat; Piano *kb; + FXKeyboard *fxkb; ListBox *lbinstruments, *lbsamples; TabBox *tabbox; GradientIcon *pixmaplogo; @@ -228,12 +231,14 @@ GUI *gui; //
Button *buttonins, *buttondel, *buttonstopnote2, *buttoncolselect, *buttonemptynote2, *buttonunmuteall; BitButton *buttonswitchmain; - Button *buttoncut, *buttoncopy, *buttonpaste, *buttonsetnotevol, *buttonseteffectcmd, *buttonseteffectpar; + Button *buttoncut, *buttoncopy, *buttonpaste, *buttonsetnotevol; Button *buttontransposedown, *buttontransposeup; + Button *buttonseteffectpar; BitButton *buttonundo, *buttonredo; PatternView *pv; - NumberSlider *nsnotevolume, *nseffectcmd, *nseffectpar; - Label *labelnotevol, *labeleffectcmd, *labeleffectpar, *labeltranspose; + NumberSlider *nsnotevolume; + DigitBox *dbeffectpar; + Label *labelnotevol, *labeleffectcmd, *labeltranspose; CheckBox *cbtoggleeffects; //
@@ -275,6 +280,8 @@ void showMessage(const char *msg, bool error); void deleteMessageBox(void); void stopPlay(void); void setHasUnsavedChanges(bool unsaved); +void handleClearFx(void); + #ifdef DEBUG @@ -294,13 +301,8 @@ void clearSubScreen(void) { u16 col = settings->getTheme()->col_bg; u32 colcol = col | col << 16; - // Fill the bg with the bg color except for the place where the keyboard is - dmaFillWords(colcol, sub_vram, 256 * 153 * 2); - for(int y=153;y<192;++y) - { - dmaFillWords(0, sub_vram + (256*y), 224 * 2); - dmaFillWords(colcol, sub_vram + (256*y) + 224, (256 - 224) * 2); - } + // Fill the bg with the bg color + dmaFillWords(colcol, sub_vram, 256 * 192 * 2); } void drawSampleNumbers(void) @@ -825,6 +827,11 @@ void setSong(Song *newsong) drawMainScreen(); } +bool areScreensSwapped(void) +{ + return !(REG_POWERCNT & POWER_SWAP_LCDS); +} + bool loadSample(const char *filename_with_path) { const char *filename = strrchr(filename_with_path, '/') + 1; @@ -1216,6 +1223,29 @@ void stopNoteStroke(void) { redraw_main_requested = true; } +void copyFxParam(void) { + u16 sel_x1, sel_y1, sel_x2, sel_y2; + uiPotSelection(&sel_x1, &sel_y1, &sel_x2, &sel_y2, false); + + // if multiple cells are selected, it's ambiguous which one they want to + // get the param of + if (sel_x1 != sel_x2 || sel_y1 != sel_y2) + return; + + Cell targetcell = song->getPattern(song->getPotEntry(state->potpos))[sel_x1][sel_y1]; + + u8 prm = targetcell.effect_param; + u8 prm2 = targetcell.effect2_param; // if no main param + + if (prm == 0 && prm2 != 0) + dbeffectpar->setValue(prm2); + else + dbeffectpar->setValue(prm); + + + redraw_main_requested = true; +} + static void actionBufferChangeCallback(void) { buttonundo->set_enabled(action_buffer->can_undo()); buttonredo->set_enabled(action_buffer->can_redo()); @@ -1267,6 +1297,12 @@ void changeOctave(u8 newoctave) drawSampleNumbers(); } +void handleEffectsCategoryChange(u8 newcat) +{ + fxkb->setCategory(newcat); + dbeffectpar->setSingleDigit(newcat == FX_CATEGORY_E); +} + void drawMainScreen(void) { @@ -1433,7 +1469,10 @@ void updateGuiToNewPattern(u8 newpattern) // Callback called from song when the pot element changes during playback void handlePotPosChangeFromSong(u16 newpotpos) -{ +{ + if (newpotpos != state->potpos) + pv->clearSelection(); + if (state->queued_potpos >= 0) { state->potpos = state->queued_potpos; state->setPlaybackRow(0); @@ -2037,7 +2076,7 @@ void setEffectCommand(u16 eff) } } -void setEffectParam(u16 eff_par) +void setEffectParam(u16 eff_par, bool new_e_cmd, bool force_clear=false, bool overwrite=true) { u16 sel_x1, sel_y1, sel_x2, sel_y2; uiPotSelection(&sel_x1, &sel_y1, &sel_x2, &sel_y2, false); @@ -2048,7 +2087,41 @@ void setEffectParam(u16 eff_par) for (u16 row = sel_y1; row <= sel_y2; row++) { Cell cell = song->getPattern(song->getPotEntry(state->potpos))[chn][row]; - cell.effect_param = eff_par; + + bool cell_has_param = cell.effect_param != 0xff && cell.effect_param != 0x0; + + + if (force_clear) + eff_par = 0x00; + // this gets messy because Exy commands use the param for both command and param info D: + else if (fxkb->getCategory() == FX_CATEGORY_E) + { + u8 ecmd_cmd = eff_par & 0xF0; + u8 ecmd_par = eff_par & 0x0F; + + if (new_e_cmd) { + // if existing fx parameter in cell, only update the E command (the first digit) + if (cell_has_param) { + ecmd_cmd = eff_par & 0xF0; + ecmd_par = cell.effect_param & 0x0F; + } + } else { + // ..or if only new param, only alter the second digit, to keep the E command + if (cell_has_param) { + ecmd_cmd = cell.effect_param & 0xF0; + ecmd_par = eff_par & 0x0F; + // ....OR if the cell has no parameter, use whatever E button they last pressed + // (otherwise 0) + } else { + ecmd_cmd = fxkb->getLastCmd() << 4; + ecmd_par = eff_par & 0x0F; + } + } + + eff_par = ecmd_cmd | ecmd_par; + } + + if (!cell_has_param || overwrite) cell.effect_param = eff_par; *fill->ptr(chn - sel_x1, row - sel_y1) = cell; } action_buffer->add(song, new MultipleCellSetAction(state, sel_x1, sel_y1, fill, false)); @@ -2089,6 +2162,7 @@ void destroyThemeDialog(void) fbtheme = 0; redrawSubScreen(); } + void reloadSkin(void) { gui->setTheme(settings->getTheme(), settings->getTheme()->col_bg); @@ -2102,6 +2176,7 @@ void reloadSkin(void) } gui->draw(); redrawSubScreen(); + setRecordMode(state->recording); redraw_main_requested = true; } @@ -2185,31 +2260,115 @@ void handleSetNoteVol(void) void handleToggleEffectsVisibility(bool on) { - pv->toggleEffectsVisibility(on); + pv->toggleEffectsVisibility(on); + + // order of hiding/showing is important to avoid + // inappropriate bg overdraw over widgets + if (on) + { + if (tbmultisample->getState()) + setMultisamplesEnabled(false); + + cbtoggleeffects->setChecked(true); + + kb->hide(); + kb->disable(); + + labeloct->hide(); + labelfxcat->show(); + numberboxoctave->hide(); + numberboxfxcat->show(); + fxkb->show(); + fxkb->enable(); + fxkb->setCategory(fxkb->getCategory()); + + dbeffectpar->show(); + labeleffectpar->show(); + buttonseteffectpar->show(); + + buttoninsnote2->hide(); + buttondelnote2->hide(); + buttonemptynote->hide(); + buttonstopnote->hide(); + + labelfxop->show(); + buttonlerpfx->show(); + buttonemptyfx->show(); + buttoncpprm->show(); + } + else + { + cbtoggleeffects->setChecked(false); + + if (!areScreensSwapped()) + pv->clearSelection(); + + kb->show(); + fxkb->hide(); + numberboxfxcat->hide(); + labelfxcat->hide(); + labeloct->show(); + numberboxoctave->show(); + kb->enable(); + fxkb->disable(); + dbeffectpar->hide(); + labeleffectpar->hide(); + buttonseteffectpar->hide(); + + labelfxop->hide(); + buttonlerpfx->hide(); + buttonemptyfx->hide(); + buttoncpprm->hide(); + + buttoninsnote2->show(); + buttondelnote2->show(); + buttonemptynote->show(); + buttonstopnote->show(); + } + + buttonundo->pleaseDraw(); // gets occluded by oct/cat label bg otherwise + + pv->recalcHscroll(); + setRecordMode(state->recording); // ensure red border gets drawn! } -// number slider -void handleEffectCommandChanged(s32 eff) -{ - setEffectCommand(eff); +void onFxKeyPressed(u8 val) +{ + if (val == NO_EFFECT || !state->recording) return; + // for E effects, the button's val is the E sub-command, rather than just 'E' + + if (fxkb->getCategory() == FX_CATEGORY_E) { + setEffectCommand(0xE); + setEffectParam((val << 4) | (dbeffectpar->getValue() & 0x0f), true); + } else { + setEffectCommand(val); + setEffectParam(dbeffectpar->getValue(), false, false, false); + } + + pv->clearSelection(); + handleNoteAdvanceRow(); } -// button -void handleSetEffectCommand(void) +// box arrows or pen slide +void handleEffectParamChanged(u8 eff_par) { - setEffectCommand(nseffectcmd->getValue()); + if (!state->recording) return; + setEffectParam(eff_par, false); } -// number slider -void handleEffectParamChanged(s32 eff_par) +// "set" button +void handleSetEffectParam(void) { - setEffectParam(eff_par); + setEffectParam(dbeffectpar->getValue(), false); + pv->clearSelection(); + handleNoteAdvanceRow(); } -// button -void handleSetEffectParam(void) +void handleClearFx(void) { - setEffectParam(nseffectpar->getValue()); + setEffectParam(0, false, true); + setEffectCommand(0xff); + pv->clearSelection(); } void showTypewriter(const char *prompt, const char *str, void (*okCallback)(void), void (*clearCallback)(void), void (*cancelCallback)(void)) { @@ -2561,12 +2720,14 @@ void switchScreens(void) { lcdSwap(); gui->switchScreens(); - pv->clearSelection(); + if (!fxkb->is_visible()) + pv->clearSelection(); redraw_main_requested = false; drawMainScreen(); } + // Create the song and do other init stuff yet to be determined. void setupSong(void) { song = new Song(6, 125); @@ -2968,6 +3129,9 @@ void setMultisamplesEnabled(bool show) { if (show) { + if (fxkb->is_visible()) + handleToggleEffectsVisibility(false); + drawSampleNumbers(); kb->showKeyLabels(); lbinstruments->resize(114, 67); @@ -2985,6 +3149,55 @@ void setMultisamplesEnabled(bool show) buttonrenamesample->set_visible(show); } +void handleLerp(void) +{ + if (!fxkb->is_visible()) return; + u16 sel_x1, sel_y1, sel_x2, sel_y2; + uiPotSelection(&sel_x1, &sel_y1, &sel_x2, &sel_y2, false); + CellArray *fill = new CellArray(sel_x2 - sel_x1 + 1, sel_y2 - sel_y1 + 1); + + Cell start = song->getPattern(song->getPotEntry(state->potpos))[sel_x1][sel_y1]; + Cell end = song->getPattern(song->getPotEntry(state->potpos))[sel_x2][sel_y2]; + + if (sel_x1 != sel_x2) + { + ntxm_dprintf("select one col only!\n"); + return; + } + + u16 starteff = start.effect_param; + u16 endeff = end.effect_param; + + u16 maxeff = std::max(starteff, endeff); + u16 mineff = std::min(starteff, endeff); + + u16 diff = std::max(sel_y1, sel_y2) - std::min(sel_y1, sel_y2); + u16 step = (maxeff - mineff) / diff; + int i = 0; + if (fill != NULL && fill->valid()) + { + for (u16 row = sel_y1; row <= sel_y2; row++) + { + Cell cell = song->getPattern(song->getPotEntry(state->potpos))[sel_x1][row]; + if (start.effect_param != end.effect_param) + { + if (starteff < endeff) + { + cell.effect_param = mineff + (step * i++); + } + else + { + cell.effect_param = maxeff - (step * i++); + } + } + *fill->ptr(sel_x1 - sel_x1, row - sel_y1) = cell; + } + action_buffer->add(song, new MultipleCellSetAction(state, sel_x1, sel_y1, fill, false)); + pv->clearSelection(); + redraw_main_requested = true; + } +} + void handleToggleMapSamples(bool is_active) { Instrument *inst = song->getInstrument(state->instrument); @@ -3163,9 +3376,14 @@ void setupGUI(bool dldi_enabled) gui->setTheme(settings->getTheme(), settings->getTheme()->col_bg); kb = new Piano(0, 152, 224, 40, (uint16*)CHAR_BASE_BLOCK_SUB(0), (uint16*)SCREEN_BASE_BLOCK_SUB(1/*8*/), &sub_vram); + kb->set_overdraw(false); kb->registerNoteCallback(handleNoteStroke); kb->registerReleaseCallback(handleNoteRelease); + fxkb = new FXKeyboard(0, 152, (uint16*)CHAR_BASE_BLOCK_SUB(0), (uint16*)SCREEN_BASE_BLOCK_SUB(1/*8*/), &sub_vram, onFxKeyPressed, false); + fxkb->set_overdraw(false); + + pixmaplogo = new GradientIcon(98, 1, 80, 17, (const u32*) nitrotracker_logo_raw, &sub_vram); pixmaplogo->registerPushCallback(showAboutBox); @@ -3675,8 +3893,11 @@ void setupGUI(bool dldi_enabled) buttonredo = new BitButton(RIGHT_SIDE_BUTTON_X + RIGHT_SIDE_BUTTON_WIDTH - 14, 127, 14, 12, &sub_vram, icon_redo_raw, 8, 8, 3, 2); buttoninsnote2 = new Button(RIGHT_SIDE_BUTTON_X, 140, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram); buttondelnote2 = new Button(RIGHT_SIDE_BUTTON_X, 153, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram); + buttonlerpfx = new Button(RIGHT_SIDE_BUTTON_X, 153, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram, false); buttonemptynote = new Button(RIGHT_SIDE_BUTTON_X, 166, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram); + buttonemptyfx = new Button(RIGHT_SIDE_BUTTON_X, 166, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram, false); buttonstopnote = new Button(RIGHT_SIDE_BUTTON_X, 179, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram); + buttoncpprm = new Button(RIGHT_SIDE_BUTTON_X, 179, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram, false); buttonrenamesample = new Button(141, 124, 23, 12, &sub_vram, false); buttonrenameinst = new Button(141, 19 , 23, 12, &sub_vram); @@ -3697,9 +3918,23 @@ void setupGUI(bool dldi_enabled) labeladd->setCaption("add"); labeloct = new Label(206, 126, 25, 12, &sub_vram, false, true); labeloct->setCaption("oct"); + labelfxcat = new Label(206, 126, 25, 12, &sub_vram, false, true); + labelfxcat->setCaption("cat"); + labeleffectpar = new Label(185, 153, 38, 10, &sub_vram, false, true, true); + labeleffectpar->set_overdraw(false); + labeleffectpar->setCaption("param"); + labelfxop = new Label(RIGHT_SIDE_BUTTON_X, 140 + 1, RIGHT_SIDE_BUTTON_WIDTH, 12, &sub_vram, false, true, true); + labelfxop->setCaption("fx op"); + numberboxfxcat = new NumberBox(206, 135, 18, 17, &sub_vram, 0, 0, 3, 1); numberboxadd = new NumberBox(185, 135, 18, 17, &sub_vram, state->add, 0, 8, 1); numberboxoctave = new NumberBox(206, 135, 18, 17, &sub_vram, state->basenote/12, 0, 6, 1); - + dbeffectpar = new DigitBox(185, 164, 35, 17, &sub_vram, 0, 0, 255, 2); + dbeffectpar->set_overdraw(false); + dbeffectpar->registerChangeCallback(handleEffectParamChanged); + buttonseteffectpar = new Button(185, 180, 35, 10, &sub_vram); + buttonseteffectpar->setCaption("set"); + buttonseteffectpar->set_overdraw(false); + buttonseteffectpar->registerPushCallback(handleSetEffectParam); buttonswitchsub->registerPushCallback(switchScreens); buttonplay->registerPushCallback(startPlay); buttonstop->registerPushCallback(stopPlay); @@ -3709,6 +3944,9 @@ void setupGUI(bool dldi_enabled) buttondelnote2->registerPushCallback(delNote); buttonemptynote->registerPushCallback(emptyNoteStroke); buttonstopnote->registerPushCallback(stopNoteStroke); + buttonlerpfx->registerPushCallback(handleLerp); + buttonemptyfx->registerPushCallback(handleClearFx); + buttoncpprm->registerPushCallback(copyFxParam); buttonrenameinst->registerPushCallback(showTypewriterForInstRename); buttonrenamesample->registerPushCallback(showTypewriterForSampleRename); @@ -3716,6 +3954,7 @@ void setupGUI(bool dldi_enabled) numberboxadd->registerChangeCallback(changeAdd); numberboxoctave->registerChangeCallback(changeOctave); + numberboxfxcat->registerChangeCallback(handleEffectsCategoryChange); lbinstruments->registerChangeCallback(handleInstChangeReset); lbsamples->registerChangeCallback(handleSampleChange); @@ -3724,6 +3963,9 @@ void setupGUI(bool dldi_enabled) buttondelnote2->setCaption("del"); buttonemptynote->setCaption("clr"); buttonstopnote->setCaption("--"); + buttonlerpfx->setCaption("lerp"); + buttonemptyfx->setCaption("clr"); + buttoncpprm->setCaption("get"); buttonrenameinst->setCaption("ren"); buttonrenamesample->setCaption("ren"); @@ -3755,32 +3997,10 @@ void setupGUI(bool dldi_enabled) buttontransposeup->setCaption("+"); buttontransposeup->registerPushCallback(handleTransposeUp); -#ifdef ENABLE_EFFECT_MENU - cbtoggleeffects = new CheckBox(195, 32, 30, 12, &main_vram_back, true, true, true); + cbtoggleeffects = new CheckBox(157, 138, 24, 12, &sub_vram, true, false, true); cbtoggleeffects->setCaption("fx"); cbtoggleeffects->registerToggleCallback(handleToggleEffectsVisibility); - labeleffectcmd = new Label(200, 44, 23, 10, &main_vram_back, false, true); - labeleffectcmd->setCaption("cmd"); - - nseffectcmd = new NumberSlider(196, 54, 28, 17, &main_vram_back, 0, -1, 26, true, true); - nseffectcmd->registerPostChangeCallback(handleEffectCommandChanged); - - buttonseteffectcmd = new Button(196, 70, 28, 12, &main_vram_back); - buttonseteffectcmd->setCaption("set"); - buttonseteffectcmd->registerPushCallback(handleSetEffectCommand); - - labeleffectpar = new Label(200, 84, 23, 10, &main_vram_back, false, true); - labeleffectpar->setCaption("val"); - - nseffectpar = new NumberSlider(196, 94, 28, 17, &main_vram_back, 0, 0, 255, true, true); - nseffectpar->registerPostChangeCallback(handleEffectParamChanged); - - buttonseteffectpar = new Button(196, 110, 28, 12, &main_vram_back); - buttonseteffectpar->setCaption("set"); - buttonseteffectpar->registerPushCallback(handleSetEffectParam); -#endif - //buttoncut = new BitButton(232, 52, 22, 21, &main_vram_back, icon_cut_raw, 16, 16, 3, 2); //buttoncopy = new BitButton(232, 74, 22, 21, &main_vram_back, icon_copy_raw, 16, 16, 3, 3); //buttonpaste = new BitButton(232, 96, 22, 21, &main_vram_back, icon_paste_raw, 16, 16, 3, 3); @@ -3827,17 +4047,7 @@ void setupGUI(bool dldi_enabled) /* gui->registerWidget(labeltranspose, 0, MAIN_SCREEN); */ gui->registerWidget(buttontransposedown, 0, MAIN_SCREEN); gui->registerWidget(buttontransposeup, 0, MAIN_SCREEN); -#ifdef ENABLE_EFFECT_MENU - gui->registerWidget(cbtoggleeffects, 0, MAIN_SCREEN); - gui->registerWidget(labeleffectcmd, 0, MAIN_SCREEN); - gui->registerWidget(nseffectcmd, 0, MAIN_SCREEN); - gui->registerWidget(buttonseteffectcmd, 0, MAIN_SCREEN); - gui->registerWidget(labeleffectpar, 0, MAIN_SCREEN); - gui->registerWidget(nseffectpar, 0, MAIN_SCREEN); - gui->registerWidget(buttonseteffectpar, 0, MAIN_SCREEN); -#else - pv->toggleEffectsVisibility(false); -#endif + gui->registerWidget(cbtoggleeffects, 0, SUB_SCREEN); gui->registerWidget(buttoncut, 0, MAIN_SCREEN); gui->registerWidget(buttoncopy, 0, MAIN_SCREEN); gui->registerWidget(buttonpaste, 0, MAIN_SCREEN); @@ -3859,14 +4069,24 @@ void setupGUI(bool dldi_enabled) gui->registerWidget(buttonredo, 0, SUB_SCREEN); gui->registerWidget(buttoninsnote2, 0, SUB_SCREEN); gui->registerWidget(buttondelnote2, 0, SUB_SCREEN); + gui->registerWidget(buttonlerpfx, 0, SUB_SCREEN); + gui->registerWidget(buttonemptyfx, 0, SUB_SCREEN); + gui->registerWidget(buttoncpprm, 0, SUB_SCREEN); gui->registerWidget(buttonrenameinst, 0, SUB_SCREEN); gui->registerWidget(buttonrenamesample, 0, SUB_SCREEN); gui->registerWidget(tbmultisample, 0, SUB_SCREEN); gui->registerWidget(numberboxadd, 0, SUB_SCREEN); gui->registerWidget(numberboxoctave, 0, SUB_SCREEN); + gui->registerWidget(numberboxfxcat, 0, SUB_SCREEN); + gui->registerWidget(dbeffectpar, 0, SUB_SCREEN); + gui->registerWidget(buttonseteffectpar, 0, SUB_SCREEN); + gui->registerWidget(labelfxop, 0, SUB_SCREEN); + gui->registerWidget(labelfxcat, 0, SUB_SCREEN); + gui->registerWidget(labeleffectpar, 0, SUB_SCREEN); gui->registerWidget(labeladd, 0, SUB_SCREEN); gui->registerWidget(labeloct, 0, SUB_SCREEN); gui->registerWidget(kb, 0, SUB_SCREEN); + gui->registerWidget(fxkb, 0, SUB_SCREEN); gui->registerWidget(buttonstopnote, 0, SUB_SCREEN); gui->registerWidget(tbrecord, 0, SUB_SCREEN); gui->registerWidget(pixmaplogo, 0, SUB_SCREEN); @@ -3875,12 +4095,12 @@ void setupGUI(bool dldi_enabled) gui->registerWidget(lbsamples, 0, SUB_SCREEN); gui->revealAll(); - handleSampleChange(0); // disable samp ed buttons at first as we have no sample! actionBufferChangeCallback(); updateTempoAndBpm(); handleLinesBeatChange(settings->getLinesPerBeat()); setHasUnsavedChanges(false); + handleToggleEffectsVisibility(false); gui->drawSubScreen(); // GUI drawMainScreen(); // Pattern view. The function also flips buffers diff --git a/arm9/source/tobkit/fxkeyboard.cpp b/arm9/source/tobkit/fxkeyboard.cpp new file mode 100644 index 0000000..fb70bd5 --- /dev/null +++ b/arm9/source/tobkit/fxkeyboard.cpp @@ -0,0 +1,305 @@ +/*==================================================================== +Copyright 2025 R Ferreira + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +======================================================================*/ + +#pragma GCC diagnostic ignored "-Wnarrowing" + +#include +#include +#include + +#include "fxkeyboard.h" +#include "effectinput.h" + +using namespace tobkit; + +/* ===================== PUBLIC ===================== */ + + +FXKeyboard::FXKeyboard(u8 _x, u8 _y, u16 *_char_base, u16 *_map_base, uint16** _vram, void (*_onFxKeypress)(u8 pressedValue), bool _visible) + : Widget(_x, _y, FXKEYBOARD_WIDTH+4, FXKEYBOARD_HEIGHT, _vram, _visible), + char_base(_char_base), map_base(_map_base), last_cmd(0), caption(0), darken_title(false), onFxKeypress(_onFxKeypress) +{ + setCaption(""); +} + +// Drawing request +void FXKeyboard::pleaseDraw(void) { + draw(); +} + +// Event calls +void FXKeyboard::penDown(u8 px, u8 py) +{ + if (px > FXBUTTON_WIDTH * NUM_FXKEYS) return; + + u8 bt_ind = ntxm_clamp(px / FXBUTTON_WIDTH, 0, NUM_FXKEYS-1); + + if (fxkb_state[bt_ind] != FXBUTTON_DISABLED) { + fxkb_state[bt_ind] |= 0x1; // set pushed + setLastCmd(fxkb_vals[bt_ind]); + useDarkTitle(false); + } else { + useDarkTitle(true); + setLastCmd(NO_EFFECT); + } + + updateCaptionForFx(fxkb_vals[bt_ind]); + draw(); +} + + +void FXKeyboard::penUp(u8 px, u8 py) +{ + for (int i=0;icol_bg, theme_->col_fxkeyboard_col1, theme_->col_fxkeyboard_col2, + theme_->col_fxkeyboard_col1_disabled, theme_->col_fxkeyboard_col2_disabled, + theme_->col_outline + }; + + genPal(fxkb_cols, fxkb_pal); + Widget::setTheme(theme_, bgcolor_); + + if (!isExposed()) return; + + memcpy(BG_PALETTE_SUB, fxkb_pal, 32); + + draw(); +} + +void FXKeyboard::hide(void) +{ + Widget::hide(); + drawFullBox(0, 0, width+FXKEYBOARD_R_OVERDRAW, height, 0x0000); // show the piano +} + +void tobkit::FXKeyboard::show(void) +{ + dmaCopy(effectinputTiles, char_base, sizeof(effectinputTiles)); + memcpy(BG_PALETTE_SUB, fxkb_pal, 32); + + Widget::show(); +} + +void FXKeyboard::setCaption(const char* _caption) { + if (caption) ntxm_free(caption); + caption = (char*)ntxm_cmalloc(strlen(_caption) + 1); + strcpy(caption, _caption); +} + +void FXKeyboard::useDarkTitle(bool _darken_title) +{ + darken_title = _darken_title; +} + +void FXKeyboard::updateCaptionForFx(u8 val) +{ + if (category == FX_CATEGORY_NORMAL) + setCaption(button_captions[val]); + else if (category == FX_CATEGORY_E) + setCaption(E_captions[val]); + + drawCaption(); +} + +void FXKeyboard::setCategory(u8 newcat) { + eraseButtonLabels(); + + category = newcat; + + useDarkTitle(false); + memset(fxkb_state, FXBUTTON_NORMAL, NUM_FXKEYS); + setCaption(category_captions[category]); + + switch (newcat) + { + case FX_CATEGORY_NORMAL: + fxkb_vals[14] = 0xF; + memset(&fxkb_state[5], FXBUTTON_DISABLED, 3); + break; + + case FX_CATEGORY_E: + fxkb_vals[14] = 0xE; + fxkb_state[0] = FXBUTTON_DISABLED; + memset(&fxkb_state[6], FXBUTTON_DISABLED, 6); + break; + + default: + useDarkTitle(true); + memset(fxkb_state, FXBUTTON_DISABLED, NUM_FXKEYS); + break; + } + + draw(); +} + +u8 FXKeyboard::getCategory(void) +{ + return category; +} + +void FXKeyboard::setLastCmd(u8 _last_cmd) +{ + last_cmd = _last_cmd; +} + +u8 FXKeyboard::getLastCmd(void) +{ + return last_cmd; +} + +/* ===================== PRIVATE ===================== */ + +void FXKeyboard::genPal(u16 *fxkb_cols_base, u16 *pal) { + for (int i = 0; i < 7; ++i) { + pal[i + 3] = interpolateColor(fxkb_cols_base[3], fxkb_cols_base[4], (4096 / 6) * i); + pal[i + 9] = interpolateColor(fxkb_cols_base[1], fxkb_cols_base[2], (4096 / 6) * i); + } + + pal[0] = fxkb_cols_base[0]; + pal[1] = fxkb_cols_base[5]; +} + +void FXKeyboard::drawCaption(void) { + if (!isExposed()) return; + + drawFullBox(1, 3, width, 5, bgcolor); + u16 capcol = darken_title ? theme->col_fxkeyboard_cmd_desc_disabled : theme->col_fxkeyboard_cmd_desc; + drawSmallString(caption, ((NUM_FXKEYS * FXBUTTON_WIDTH) / 2) - (2 * strlen(caption)) + 4, 3, capcol); // width=(3px char+1px space) / 2 +} + +void FXKeyboard::drawButtonLabel(u8 key, u8 cat, bool visible) +{ + u8 xpos = key * FXBUTTON_WIDTH + 3; + u16 col = theme->col_fxkeyboard_btn_label; + u16 smallcol1 = theme->col_fxkeyboard_minilabel_x; + u16 smallcol2 = theme->col_fxkeyboard_minilabel_y; + + if(visible == true) { + col |= BIT(15); + smallcol1 |= BIT(15); + smallcol2 |= BIT(15); + } + + char label[] = {fxlabels[cat][key], 0}; + + drawString(label, xpos, 15, col); + + char small_caption[3] = {0}; // one wasted byte...noone will notice + + if (cat == FX_CATEGORY_E) + snprintf(small_caption, 3, "%1XX", fxkb_vals[key]); + else if (cat == FX_CATEGORY_NORMAL) + sprintf(small_caption, "X%s", ((labels_cat0 >> key) & 0x1) ? "Y" : "X"); + else + return; // buttons in the other two categories do not have labels yet + + // the small "XX"/"XY" to show parameter format + drawSmallChar(GLYPH_3X5(small_caption[0]), xpos, 27, smallcol1); + drawSmallChar(GLYPH_3X5(small_caption[1]), xpos + 4, 27, small_caption[1] == 'Y' ? smallcol2 : smallcol1); +} + +void FXKeyboard::drawButtonLabels(void) +{ + for (int i=0;ichannel - hscrollpos; + + if (effects_visible && rel_cursor_x > getNumVisibleChannels()) + { + hscrollpos = state->channel - getNumVisibleChannels(); + } +} /* ===================== PRIVATE ===================== */ // This is a fullscreen widget so it does not give a damn diff --git a/arm9/source/tobkit/patternview.h b/arm9/source/tobkit/patternview.h index c883d52..93663bc 100644 --- a/arm9/source/tobkit/patternview.h +++ b/arm9/source/tobkit/patternview.h @@ -29,7 +29,6 @@ #include "ntxm/song.h" #include "../state.h" -#include "font_3x5_raw.h" namespace tobkit { @@ -70,12 +69,29 @@ namespace tobkit { #define G 16 #define H 17 +#define L 21 +#define M 22 +#define P 25 +#define R 27 +#define S 28 +#define U 30 +#define V 31 + #define DOT 36 #define MINUS 37 #define SHARP 38 #define SPACE 39 -#define GLYPH_3X5_COUNT 40 +#define VSLIDEUP 41 +#define VSLIDEDOWN MINUS +#define FVSLIDEDOWN 43 +#define FVSLIDEUP 42 +#define SETPANPOS P +#define PANSLIDELEFT 44 +#define PANSLIDERIGHT 45 +#define NOTEPORTA M +#define SETVIBRATOSPD S +#define SETVIBRATO V const u8 notes_chars[] = {12, 12, 13, 13, 14, 15, 15, 16, 16, 10, 10, 17}; const u8 notes_signs[] = {0 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 0 , 1 , 0}; @@ -140,71 +156,18 @@ class PatternView: public Widget { col_effect_dark = theme_->col_pv_effect_dark; col_effect_param_dark = theme_->col_pv_effect_param_dark; } + void recalcHscroll(void); private: void draw(void); - // Draw a 3x5 character at gridpos (cx,cy) - inline void drawChar(u8 c, u8 cx, u8 cy, u16 col) - { - /* - // Ugly quick code (which also does not work) - TODO: Fix this and test if its quicker - u8 i; - for(i=0;i<15;++i) { - if(font_3x5[(3*22*i/3)+3*c+i%3] & BIT(15)) - *(*vram+SCREEN_WIDTH*(cy*6+i/3)+cx*4+i%3) = RGB15(0,0,0)|BIT(15); - } - */ - - // Readable slow code - /* - u8 xpos = cx*4; - u8 ypos = cy*6; - - u8 i,j; - u16 px; - for(j=0;j<5;++j) { - for(i=0;i<3;++i) { - px = font_3x5[3*22*j+3*c+i]; - if(px&BIT(15)) { - *(*vram+SCREEN_WIDTH*(ypos+j)+xpos+i) = RGB15(0,0,0)|BIT(15); - } - } - } - */ - - // Semi-readable code with less variables. OK for now. - /* - u8 i,j; - for(j=0;j<5;++j) { - for(i=0;i<3;++i) { - if(font_3x5[3*22*j+3*c+i]&BIT(15)) { - *(*vram+SCREEN_WIDTH*(2+cy*8+j)+1+cx*4+i) = col; - } - } - } - */ - - // Rather hard to understand code that uses the new size-efficient bitmap-font (8 pixels/byte) - u8 i,j; - for(j=0;j<5;++j) { - for(i=0;i<3;++i) { - u16 pixelidx = 3*GLYPH_3X5_COUNT*j+3*c+i; - if(font_3x5_raw[pixelidx/8]&BIT(pixelidx%8)) { - //*(*vram+SCREEN_WIDTH*(2+cy*8+j)+1+cx*4+i) = col; - *(*vram+SCREEN_WIDTH*(y+cy+j)+x+cx+i) = col; - } - } - } - } inline void drawHexByte(u8 byte, u8 cx, u8 cy, u16 col) { - //drawChar(byte/0x10, cx , cy, col); - //drawChar(byte%0x10, cx+1, cy, col); - drawChar(byte/0x10, cx , cy, col); - drawChar(byte%0x10, cx+PV_CHAR_WIDTH, cy, col); + //drawSmallChar(byte/0x10, cx , cy, col); + //drawSmallChar(byte%0x10, cx+1, cy, col); + drawSmallChar(byte/0x10, cx , cy, col); + drawSmallChar(byte%0x10, cx+PV_CHAR_WIDTH, cy, col); } inline void drawCell(u8 cellx, u8 celly, u8 px, u8 py, bool dark) @@ -231,32 +194,72 @@ class PatternView: public Widget { // Check for empty note or stop-note if(cell->note == STOP_NOTE) { - drawChar(DOT, realx , realy, notecol); - drawChar(MINUS, realx+1*PV_CHAR_WIDTH, realy, notecol); - drawChar(DOT, realx+2*PV_CHAR_WIDTH, realy, notecol); + drawSmallChar(DOT, realx , realy, notecol); + drawSmallChar(MINUS, realx+1*PV_CHAR_WIDTH, realy, notecol); + drawSmallChar(DOT, realx+2*PV_CHAR_WIDTH, realy, notecol); } else if(cell->note != EMPTY_NOTE) { // Note - drawChar(notes_chars[cell->note%12], realx, realy, notecol); + drawSmallChar(notes_chars[cell->note%12], realx, realy, notecol); if(notes_signs[cell->note%12]) { - drawChar(SHARP, realx+1*PV_CHAR_WIDTH, realy, notecol); + drawSmallChar(SHARP, realx+1*PV_CHAR_WIDTH, realy, notecol); } else { - drawChar(MINUS, realx+1*PV_CHAR_WIDTH, realy, notecol); + drawSmallChar(MINUS, realx+1*PV_CHAR_WIDTH, realy, notecol); } - drawChar(cell->note/12, realx+2*PV_CHAR_WIDTH, realy, notecol); + drawSmallChar(cell->note/12, realx+2*PV_CHAR_WIDTH, realy, notecol); } // Instrument if(cell->instrument != NO_INSTRUMENT) drawHexByte(cell->instrument+1, realx+3*PV_CHAR_WIDTH+1, realy, instrcol); // Adding one because FT2 indices start with 1 - // Volume - if(cell->volume != NO_VOLUME) - drawHexByte(cell->volume, realx+5*PV_CHAR_WIDTH+2, realy, volumecol); + if (cell->volume != NO_VOLUME) + { + drawHexByte(cell->volume, realx + 5 * PV_CHAR_WIDTH + 2, realy, volumecol); + } + + // volume effect column slightly buggy and needs + // song.cpp adjustment in libntxm, lets leave that commented for now + + // else { + // if (cell->effect2 != 0xff && cell->volume == NO_VOLUME) + // { + // char eff; + // u8 vol = cell->volume_raw; + + // if ((vol >= 0x60) && (vol <= 0x6F)) // Volume slide down + // eff = VSLIDEDOWN; + // else if ((vol >= 0x70) && (vol <= 0x7F)) // Volume slide up + // eff = VSLIDEUP; + // else if ((vol >= 0x80) && (vol <= 0x8F)) // Fine volume slide down + // eff = FVSLIDEDOWN; + // else if ((vol >= 0x90) && (vol <= 0x9F)) // Fine volume slide up + // eff = FVSLIDEUP; + // else if ((vol >= 0xA0) && (vol <= 0xAF)) // Set vibrato speed (calls vibrato) + // eff = SETVIBRATOSPD; + // else if ((vol >= 0xB0) && (vol <= 0xBF)) // Vibrato + // eff = SETVIBRATO; + // else if ((vol >= 0xC0) && (vol <= 0xCF)) // Set panning + // eff = SETPANPOS; + // else if ((vol >= 0xD0) && (vol <= 0xDF)) // Panning slide left + // eff = PANSLIDELEFT; + // else if ((vol <= 0x1f)) // Panning slide right + // eff = PANSLIDERIGHT; + // else if (vol >= 0xF0) // Tone porta + // eff = NOTEPORTA; + // else { + // eff = 0; + // } + // if (eff != 0) { + // drawSmallChar(eff, realx + 5 * PV_CHAR_WIDTH + 2, realy, effectcol); + // drawSmallChar(cell->effect2_param & 0x0F, realx + 6 * PV_CHAR_WIDTH + 2, realy, effectparamcol); + // } + // } + // } if(effects_visible) { // Effect and effect parameter if (cell->effect != 0xff) - drawChar(cell->effect, realx+7*PV_CHAR_WIDTH+3, realy, effectcol); + drawSmallChar(cell->effect, realx+7*PV_CHAR_WIDTH+3, realy, effectcol); if (cell->effect_param != 0x00) drawHexByte(cell->effect_param, realx+8*PV_CHAR_WIDTH+3, realy, effectparamcol); diff --git a/themes/Concrete.nttheme b/themes/Concrete.nttheme index 9e1aa9b..164caef 100644 --- a/themes/Concrete.nttheme +++ b/themes/Concrete.nttheme @@ -87,3 +87,4 @@ 95=555555 ; Text entry key labels 96=555555 ; Text entry modifier keys 97=ffdd00 ; Text entry pressed keys +109=333333 ; FX input keyboard command description label (disabled) \ No newline at end of file diff --git a/themes/Default.nttheme b/themes/Default.nttheme index 3a68020..7151484 100644 --- a/themes/Default.nttheme +++ b/themes/Default.nttheme @@ -100,4 +100,13 @@ 99=8394c5 ; Sample editor zoom buttons 100=e67b00 ; Message box title bar gradient 1 101=e6e600 ; Message box title bar gradient 2 -102=000000 ; Message box title bar text \ No newline at end of file +102=000000 ; Message box title bar text +103=ffffff ; FX input keyboard button gradient 1 +104=cbcbcb ; FX input keyboard button gradient 2 +105=8394c5 ; FX input keyboard button gradient 1 (disabled) +106=4a5a8b ; FX input keyboard button gradient 2 (disabled) +107=000000 ; FX input keyboard button command label +108=8394c5 ; FX input keyboard command description label +109=ff9400 ; FX input keyboard command description label (disabled) +110=4a7bff ; FX input keyboard button "X" label +111=ff62ee ; FX input keyboard button "Y" label \ No newline at end of file diff --git a/themes/Neon.nttheme b/themes/Neon.nttheme index 9f47d48..4ccfd77 100644 --- a/themes/Neon.nttheme +++ b/themes/Neon.nttheme @@ -96,4 +96,8 @@ 95=fd5ac1 ; Text entry key labels 96=9391f6 ; Text entry modifier keys 97=01eded ; Text entry pressed keys -98=0f1519 ; Text entry modifier key labels \ No newline at end of file +98=0f1519 ; Text entry modifier key labels +103=9391f6 ; FX input keyboard button gradient 1 +104=4949c6 ; FX input keyboard button gradient 2 +105=334566 ; FX input keyboard button gradient 1 (disabled) +106=1d2030 ; FX input keyboard button gradient 2 (disabled) \ No newline at end of file diff --git a/themes/Velocity.nttheme b/themes/Velocity.nttheme index 5d916ea..1a8a679 100644 --- a/themes/Velocity.nttheme +++ b/themes/Velocity.nttheme @@ -87,4 +87,5 @@ 95=ffffff ; Text entry key labels 96=7d92aa ; Text entry modifier keys 97=becbdb ; Text entry pressed keys -95=ffffff ; Text entry key modifier key labels \ No newline at end of file +95=ffffff ; Text entry key modifier key labels +109=161d1d ; FX input keyboard command description label (disabled) \ No newline at end of file diff --git a/themes/bye2/bye2theme.nttheme b/themes/bye2/bye2theme.nttheme index f1f0835..1623659 100644 --- a/themes/bye2/bye2theme.nttheme +++ b/themes/bye2/bye2theme.nttheme @@ -87,4 +87,6 @@ 95=000000 ; Text entry key labels 96=392e5b ; Text entry modifier keys 97=14ff8b ; Text entry pressed keys -98=000000 ; Text entry key labels \ No newline at end of file +98=000000 ; Text entry key labels +108=14ff8b ; FX input keyboard command description label +109=5a4a84 ; FX input keyboard command description label (disabled) \ No newline at end of file diff --git a/tobkit/Makefile b/tobkit/Makefile index e930cc5..8ef66b4 100644 --- a/tobkit/Makefile +++ b/tobkit/Makefile @@ -203,6 +203,13 @@ $(BUILDDIR)/fonts/font_8x11.raw.o : fonts/font_8x11.png fonts/font_8x11.png.txt $(V)$(BLOCKSDS)/tools/bin2c/bin2c $(basename $(basename $@)).raw $(@D) $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(basename $(basename $@)).raw.o $(basename $(basename $@))_raw.c +$(BUILDDIR)/fonts/font_3x5.raw.o : fonts/font_3x5.png + @echo $(notdir $<) + @$(MKDIR) -p $(@D) + $(V)python3 $(TOOLSDIR)/font_3x5_pack.py $< $(basename $(basename $@)).raw + $(V)$(BLOCKSDS)/tools/bin2c/bin2c $(basename $(basename $@)).raw $(@D) + $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(basename $(basename $@)).raw.o $(basename $(basename $@))_raw.c + $(BUILDDIR)/%.raw.o : %.png @echo $(notdir $<) @$(MKDIR) -p $(@D) diff --git a/tobkit/fonts/font_3x5.png b/tobkit/fonts/font_3x5.png new file mode 100644 index 0000000..62bee68 Binary files /dev/null and b/tobkit/fonts/font_3x5.png differ diff --git a/tobkit/include/tobkit/digitbox.h b/tobkit/include/tobkit/digitbox.h new file mode 100644 index 0000000..9a2e852 --- /dev/null +++ b/tobkit/include/tobkit/digitbox.h @@ -0,0 +1,57 @@ +/*==================================================================== +Copyright 2025 R Ferreira + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +======================================================================*/ + +#ifndef DIGITBOX_H +#define DIGITBOX_H + +#include "widget.h" +#include + +namespace tobkit { + +class DigitBox: public Widget { + public: + DigitBox(u8 _x, u8 _y, u8 _width, u8 _height, uint16 **_vram, u8 _value=0, u8 _min=0, u8 _max=255, u8 _digits=2); + + // Drawing request + void pleaseDraw(void); + + // Event calls + void penDown(u8 px, u8 py); + void penUp(u8 px, u8 py); + void penMove(u8 px, u8 py); + + void setValue(u8 val); + u8 getValue(void); + + void setSingleDigit(bool single_digit_mode); + + // Callback registration + void registerChangeCallback(void (*onChange_)(u8)); + private: + void draw(void); + + void (*onChange)(u8); + + u8 value; + u8 min, max, digits; + u8 btnstate; + u8 lasty, lastx; +}; + +}; + +#endif diff --git a/tobkit/include/tobkit/piano.h b/tobkit/include/tobkit/piano.h index 021234a..79f3926 100644 --- a/tobkit/include/tobkit/piano.h +++ b/tobkit/include/tobkit/piano.h @@ -47,7 +47,8 @@ class Piano: public Widget { void setKeyLabel(u8 key, char label); void setInMappingMode(bool instmap); void setTheme(Theme *theme_, u16 bgcolor_); - + void show(void); + private: void (*onNote)(u8); void (*onRelease)(u8, bool); diff --git a/tobkit/include/tobkit/theme.h b/tobkit/include/tobkit/theme.h index 4c0f055..b2920a3 100644 --- a/tobkit/include/tobkit/theme.h +++ b/tobkit/include/tobkit/theme.h @@ -22,7 +22,7 @@ limitations under the License. #include "../../arm9/source/tools.h" #include -#define NUM_COLORS 103 +#define NUM_COLORS 112 namespace tobkit { @@ -133,6 +133,15 @@ struct ColorScheme { u16 col_messagebox_title_col1; u16 col_messagebox_title_col2; u16 col_messagebox_title_text; + u16 col_fxkeyboard_col1; + u16 col_fxkeyboard_col2; + u16 col_fxkeyboard_col1_disabled; + u16 col_fxkeyboard_col2_disabled; + u16 col_fxkeyboard_btn_label; + u16 col_fxkeyboard_cmd_desc; + u16 col_fxkeyboard_cmd_desc_disabled; + u16 col_fxkeyboard_minilabel_x; + u16 col_fxkeyboard_minilabel_y; }; }; diff --git a/tobkit/include/tobkit/typewriter.h b/tobkit/include/tobkit/typewriter.h index 5fd6def..c6313d3 100644 --- a/tobkit/include/tobkit/typewriter.h +++ b/tobkit/include/tobkit/typewriter.h @@ -57,7 +57,7 @@ class Typewriter: public Widget { void setTheme(Theme *theme_, u16 bgcolor_); private: - unsigned short typewriterPal[16] __attribute__((aligned(4))) __attribute__((visibility("hidden"))); + unsigned short typewriterPal[16] __attribute__((aligned(4))); void (*onOk)(void); diff --git a/tobkit/include/tobkit/widget.h b/tobkit/include/tobkit/widget.h index de121a6..8c5ea21 100644 --- a/tobkit/include/tobkit/widget.h +++ b/tobkit/include/tobkit/widget.h @@ -36,6 +36,10 @@ struct Font { const u8* data; }; +#define GLYPH_3X5_COUNT 47 +#define GLYPH_3X5(c) (c == ':') ? 40 : ((c < 58 && c > 47) ? (c - 48) : (c - 55)) +#define GLYPH_3X5_WIDTH 4 + class Widget { public: // Constructor sets base variables @@ -62,6 +66,7 @@ class Widget { virtual void hide(void); bool is_visible(void) { return visible; } bool set_visible(bool value); + void set_overdraw(bool value); virtual void occlude(void); virtual void reveal(void); @@ -89,12 +94,27 @@ class Widget { protected: u16 x, y, width, height; bool enabled; + bool do_overdraw; u16 **vram; Theme *theme; u16 bgcolor; // Color of the background (for hiding the widget) // Draw utility functions void drawString(const char* str, u8 tx, u8 ty, u16 color, u8 maxwidth=255, u8 maxheight=255); + + inline void drawSmallString(const char *message, u8 sx, u8 sy, u16 col) + { + size_t n_chars = strlen(message); + for (size_t c = 0; c < n_chars; ++c) + { + char _c = message[c]; + if (_c == ' ') continue; + + drawSmallChar(GLYPH_3X5(_c), sx + c * GLYPH_3X5_WIDTH, sy, col); + } + } + + void drawSmallChar(u8 c, u8 cx, u8 cy, u16 col); void drawBox(u8 tx, u8 ty, u8 tw, u8 th, u16 col); void drawFullBox(u8 tx, u8 ty, u8 tw, u8 th, u16 col); void drawBorder(u16 col); diff --git a/tobkit/source/digitbox.cpp b/tobkit/source/digitbox.cpp new file mode 100644 index 0000000..db69f10 --- /dev/null +++ b/tobkit/source/digitbox.cpp @@ -0,0 +1,226 @@ +/*==================================================================== +Copyright 2025 R Ferreira + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +======================================================================*/ + +/* +like a numberbox, but it has two pairs of arrows, +and you can slide on it +*/ + +#include +#include + +#include "tobkit/digitbox.h" + +using namespace tobkit; + +#define DB_MARGIN_TOP 12 + +/* ===================== PUBLIC ===================== */ + +DigitBox::DigitBox(u8 _x, u8 _y, u8 _width, u8 _height, uint16 **_vram, u8 _value, u8 _min, u8 _max, u8 _digits) + :Widget(_x, _y - DB_MARGIN_TOP, _width, _height + DB_MARGIN_TOP, _vram), + value(_value), min(_min), max(_max), digits(_digits), btnstate(0), lasty(0), lastx(0) +{ + onChange = 0; +} + +void DigitBox::penMove(u8 px, u8 py) +{ + if (!enabled) return; + + if (lasty == 0) + { + lasty=py; + return; + } + + s16 dy = lasty-py; + if(abs(dy)>0 && px > x + 8 && px < x + width - 8) { + int inc = (dy*dy) >> 3; + if (dy == 0) + inc = 1; + if(dy < 0) + inc = -(inc * 2); + + s16 newval = value+inc; + + if(newval > max) { + value=max; + } else if(newvalx)&&(pxy)&&(py max-0x10) value = value + 0x10 - max - 1; // wrap around if (value + 0x10) > 0xff. so 0xf0 wraps to 0x00 + else value+=0x10; + } else if((px>x)&&(pxy+9)&&(pyx)&&(px>x+(width/4)*3)&&(py>y)&&(pyx)&&(px>x+(width/4)*3)&&(py>y+9)&&(pymin) value--; else value = max; + } + + if(value!=oldvalue) { + onChange(value); + draw(); + } +} + +void DigitBox::penUp(u8 px, u8 py) { + lasty=0; + btnstate = 0; + draw(); +} + +void DigitBox::setValue(u8 val) +{ + s32 oldval = value; + + if(val > max) + value = max; + else if (val < min) + value = min; + else + value = val; + + if(oldval != value) + { + if(onChange!=0) + onChange(val); + if(isExposed()) + draw(); + } +} + +u8 DigitBox::getValue(void) +{ + return value; +} + +void DigitBox::setSingleDigit(bool single_digit_mode) +{ + digits = single_digit_mode ? 1 : 2; + + if (theme) + pleaseDraw(); +} + +// Callback registration +void DigitBox::registerChangeCallback(void (*onChange_)(u8)) { + onChange = onChange_; +} + +/* ===================== PRIVATE ===================== */ + +void DigitBox::draw(void) +{ + // Number display + drawFullBox(9, 1 + DB_MARGIN_TOP, width-9, 17-1, theme->col_lighter_bg); + + u8 extra_offs = 0; + char numberstr[5]; + + char formatstr2[] = "%02x"; + char formatstr1[] = "%1x"; + if (digits != 2) + { + extra_offs = 3; + } + + // Set no of digits (hacky, but there's no other way) + // formatstr[1] = digits+48; + snprintf(numberstr, sizeof(numberstr), digits == 2 ? formatstr2 : formatstr1, (digits == 2) ? value : (value & 0x0f)); + drawString(numberstr, 11 + extra_offs, 5 + DB_MARGIN_TOP, theme->col_text_value); + + // Probably quite dumb but avoids code duplication lol + // k=0: left hand side of arrow buttons, k=1: right hand side of arrow buttons + for (int k = 0; k < 2; ++k) + { + bool disable_left = (k == 0 && digits == 1); + + u8 offset = k * (width - 9); // right arrow pair offset + + u16 col1 = disable_left ? theme->col_dark_ctrl_disabled : theme->col_dark_ctrl; + u16 col2 = disable_left ? theme->col_light_ctrl_disabled : theme->col_light_ctrl; + + // Upper Button + if(btnstate==1 + 2 * k) { // button is currently pressed + drawGradient(col1, col2, 1+offset, 1 + DB_MARGIN_TOP, 8, 8); + } else { + drawGradient(col2, col1, 1+offset, 1 + DB_MARGIN_TOP, 8, 8); + } + + // This draws the up-arrow + int_fast8_t i,j; + for(j=0;j<3;j++) { + for(i=-j;i<=j;++i) { + drawPixel(4+i+offset, j+3+DB_MARGIN_TOP, theme->col_text_bt); + } + } + + drawBox(0+offset, 0+DB_MARGIN_TOP, 9, 9, theme->col_outline); + + // Lower Button + if(btnstate==2 + 2 * k) { + drawGradient(col1, col2, 1+offset, 8+DB_MARGIN_TOP, 8, 8); + } else { + drawGradient(col2, col1, 1+offset, 8+DB_MARGIN_TOP, 8, 8); + } + + // This draws the down-arrow + for(j=2;j>=0;j--) { + for(i=-j;i<=j;++i) { + drawPixel(4+i+offset, -j+13+DB_MARGIN_TOP, theme->col_text_bt); + } + } + + drawBox(0+offset, 8+DB_MARGIN_TOP, 9, 9, theme->col_outline); + } + + + + // Border + drawBox(0, 0+DB_MARGIN_TOP, width, height-DB_MARGIN_TOP, theme->col_outline); +} diff --git a/tobkit/source/piano.cpp b/tobkit/source/piano.cpp index cefc846..bd252f3 100644 --- a/tobkit/source/piano.cpp +++ b/tobkit/source/piano.cpp @@ -51,13 +51,27 @@ void Piano::setTheme(Theme *theme_, u16 bgcolor_) { theme_->col_piano_half_highlight_col2, theme_->col_piano_outline}; genPal(piano_cols, piano_Palette, piano_fullnotehighlight_Palette, piano_halfnotehighlight_Palette); Widget::setTheme(theme_, bgcolor_); - memcpy(BG_PALETTE_SUB, piano_Palette, 32); + + // doesn't conflict with fx keyboard, so safe to write these memcpy(BG_PALETTE_SUB+16, piano_fullnotehighlight_Palette, 32); memcpy(BG_PALETTE_SUB+32, piano_halfnotehighlight_Palette, 32); + + if (!isExposed()) return; + + memcpy(BG_PALETTE_SUB, piano_Palette, 32); + + setInMappingMode(mapping_instrument); pleaseDraw(); } +void Piano::show(void) +{ + dmaCopy(pianoTiles, char_base, sizeof(pianoTiles)); + memcpy(BG_PALETTE_SUB, piano_Palette, 32); + + Widget::show(); +} // Drawing request void Piano::pleaseDraw(void) { diff --git a/tobkit/source/theme.cpp b/tobkit/source/theme.cpp index 9567537..a323010 100644 --- a/tobkit/source/theme.cpp +++ b/tobkit/source/theme.cpp @@ -124,6 +124,15 @@ ColorScheme::ColorScheme() { col_messagebox_title_col1 = col_list_highlight1; col_messagebox_title_col2 = col_list_highlight2; col_messagebox_title_text = col_text; + col_fxkeyboard_col1 = col_piano_full_col1; + col_fxkeyboard_col2 = col_piano_full_col2; + col_fxkeyboard_col1_disabled = col_light_ctrl_disabled; + col_fxkeyboard_col2_disabled = col_dark_ctrl_disabled; + col_fxkeyboard_btn_label = col_piano_label; + col_fxkeyboard_cmd_desc = col_text_light; + col_fxkeyboard_cmd_desc_disabled = col_dark_ctrl_disabled; + col_fxkeyboard_minilabel_x = col_pv_notes &~ BIT(15); + col_fxkeyboard_minilabel_y = col_pv_effect &~ BIT(15); } Theme::Theme(char* themepath, bool use_fat) @@ -145,6 +154,9 @@ bool Theme::loadTheme(const char* themefile) { memcpy(data, scheme.data, sizeof(data)); col_piano_label &= ~BIT(15); col_piano_label_inv &= ~BIT(15); + col_fxkeyboard_btn_label &= ~BIT(15); + col_fxkeyboard_minilabel_x &= ~BIT(15); + col_fxkeyboard_minilabel_y &= ~BIT(15); debugprintf("loaded theme '%s'\n", themefile); return true; @@ -227,5 +239,15 @@ bool Theme::parseTheme(FILE* theme_, u16* theme_cols) { if (!theme_has_key[101]) theme_cols[101] = theme_cols[14]; // Message box title gradient col2 if (!theme_has_key[102]) theme_cols[102] = theme_cols[27]; // Message box title text + if (!theme_has_key[103]) theme_cols[103] = theme_cols[84]; // Fxkb button gradient 1 + if (!theme_has_key[104]) theme_cols[104] = theme_cols[85]; // Fxkb button gradient 2 + if (!theme_has_key[105]) theme_cols[105] = theme_cols[7]; // Fxkb disabled button gradient 1 + if (!theme_has_key[106]) theme_cols[106] = theme_cols[8]; // Fxkb disabled button gradient 2 + if (!theme_has_key[107]) theme_cols[107] = theme_cols[35]; // Fxkb big button label + if (!theme_has_key[108]) theme_cols[108] = theme_cols[28]; // Fxkb command desc label + if (!theme_has_key[109]) theme_cols[109] = theme_cols[8]; // Fxkb disabled command desc label + if (!theme_has_key[110]) theme_cols[110] = theme_cols[61]; // Fxkb button param label 'X' + if (!theme_has_key[111]) theme_cols[111] = theme_cols[67]; // Fxkb button param label 'Y' + return true; } diff --git a/tobkit/source/widget.cpp b/tobkit/source/widget.cpp index 4fe2d9c..4e4110c 100644 --- a/tobkit/source/widget.cpp +++ b/tobkit/source/widget.cpp @@ -22,13 +22,15 @@ limitations under the License. using namespace tobkit; #include "font_8x11.inc" +#include "font_3x5_raw.h" + #define abs(x) (x<0?(-x):(x)) /* ===================== PUBLIC ===================== */ Widget::Widget(u8 _x, u8 _y, u8 _width, u8 _height, u16 **_vram, bool _visible, bool _occluded) - :x(_x), y(_y), width(_width), height(_height), enabled(true), vram(_vram), visible(_visible), occluded(_occluded) + :x(_x), y(_y), width(_width), height(_height), enabled(true), do_overdraw(true), vram(_vram), visible(_visible), occluded(_occluded) { } @@ -133,6 +135,10 @@ bool Widget::set_enabled(bool value) return changed; } +void Widget::set_overdraw(bool value) +{ + do_overdraw = value; +} /* ===================== PROTECTED ===================== */ // Draw utility functions @@ -172,6 +178,21 @@ void Widget::drawString(const char* str, u8 tx, u8 ty, u16 color, u8 maxwidth, u } } +ITCM_CODE +void Widget::drawSmallChar(u8 c, u8 cx, u8 cy, u16 col) +{ + u8 i,j; + for(j=0;j<5;++j) { + for(i=0;i<3;++i) { + u16 pixelidx = 3*GLYPH_3X5_COUNT*j+3*c+i; + if(font_3x5_raw[pixelidx/8]&BIT(pixelidx%8)) { + //*(*vram+SCREEN_WIDTH*(2+cy*8+j)+1+cx*4+i) = col; + *(*vram+SCREEN_WIDTH*(y+cy+j)+x+cx+i) = col; + } + } + } +} + ITCM_CODE void Widget::drawBox(u8 tx, u8 ty, u8 tw, u8 th, u16 col) { @@ -387,5 +408,6 @@ bool Widget::isExposed(void) void Widget::overdraw(void) { - drawFullBox(0, 0, width, height, bgcolor); + if (do_overdraw) + drawFullBox(0, 0, width, height, bgcolor); }