void onExpoMixMenu(const char *result) { bool expo = (g_menuStack[g_menuStackPtr] == menuModelExposAll); uint8_t chn = (expo ? expoAddress(s_currIdx)->chn+1 : mixAddress(s_currIdx)->destCh+1); if (result == STR_EDIT) { pushMenu(expo ? menuModelExpoOne : menuModelMixOne); } else if (result == STR_INSERT_BEFORE || result == STR_INSERT_AFTER) { if (!reachExpoMixCountLimit(expo)) { s_currCh = chn; if (result == STR_INSERT_AFTER) { s_currIdx++; m_posVert++; } insertExpoMix(expo, s_currIdx); pushMenu(expo ? menuModelExpoOne : menuModelMixOne); } } else if (result == STR_COPY || result == STR_MOVE) { s_copyMode = (result == STR_COPY ? COPY_MODE : MOVE_MODE); s_copySrcIdx = s_currIdx; s_copySrcCh = chn; s_copySrcRow = m_posVert; } else if (result == STR_DELETE) { deleteExpoMix(expo, s_currIdx); } }
void insertExpoMix(uint8_t expo, uint8_t idx) { pauseMixerCalculations(); if (expo) { ExpoData *expo = expoAddress(idx); memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); memclear(expo, sizeof(ExpoData)); expo->srcRaw = (s_currCh > 4 ? MIXSRC_Rud - 1 + s_currCh : MIXSRC_Rud - 1 + channel_order(s_currCh)); expo->curve.type = CURVE_REF_EXPO; expo->mode = 3; // pos&neg expo->chn = s_currCh - 1; expo->weight = 100; } else { MixData *mix = mixAddress(idx); memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); memclear(mix, sizeof(MixData)); mix->destCh = s_currCh-1; mix->srcRaw = s_currCh; if (!isSourceAvailable(mix->srcRaw)) { mix->srcRaw = (s_currCh > 4 ? MIXSRC_Rud - 1 + s_currCh : MIXSRC_Rud - 1 + channel_order(s_currCh)); while (!isSourceAvailable(mix->srcRaw)) { mix->srcRaw += 1; } } mix->weight = 100; } resumeMixerCalculations(); eeDirty(EE_MODEL); }
int16_t expoFn(int16_t x) { ExpoData *ed = expoAddress(s_currIdx); int16_t anas[NUM_INPUTS] = {0}; applyExpos(anas, e_perout_mode_inactive_flight_mode, ed->srcRaw, x); return anas[ed->chn]; }
static unsigned int getInputsCountFromFirst(unsigned int chn, unsigned int first) { unsigned int count = 0; for (unsigned int i=first; i<MAX_INPUTS; i++) { ExpoData * expo = expoAddress(i); if (!expo->srcRaw || expo->chn!=chn) break; count++; } return count; }
static unsigned int getFirstInput(unsigned int chn) { for (unsigned int i=0; i<MAX_INPUTS; i++) { ExpoData * expo = expoAddress(i); if (!expo->srcRaw || expo->chn >= chn) { return i; } } return 0; }
bool isInputAvailable(int input) { for (int i=0; i<MAX_EXPOS; i++) { ExpoData * expo = expoAddress(i); if (!EXPO_VALID(expo)) break; if (expo->chn == input) return true; } return false; }
uint8_t getExpoMixCount(uint8_t expo) { uint8_t count = 0; uint8_t ch ; for (int i=(expo ? MAX_EXPOS-1 : MAX_MIXERS-1); i>=0; i--) { ch = (expo ? EXPO_VALID(expoAddress(i)) : mixAddress(i)->srcRaw); if (ch != 0) { count++; } } return count; }
void copyExpoMix(uint8_t expo, uint8_t idx) { pauseMixerCalculations(); if (expo) { ExpoData *expo = expoAddress(idx); memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); } else { MixData *mix = mixAddress(idx); memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); } resumeMixerCalculations(); eeDirty(EE_MODEL); }
TEST(Trims, InstantTrimNegativeCurve) { MODEL_RESET(); modelDefault(0); ExpoData *expo = expoAddress(AIL_STICK); expo->curve.type = CURVE_REF_CUSTOM; expo->curve.value = 1; g_model.points[0] = -100; g_model.points[1] = -75; g_model.points[2] = -50; g_model.points[3] = -25; g_model.points[4] = 0; anaInValues[AIL_STICK] = 512; instantTrim(); EXPECT_EQ(128, getTrimValue(0, AIL_STICK)); }
/*luadoc @function model.getInput(input, line) Returns input data for given input and line number @param input (unsigned number) input number (use 0 for Input1) @param line (unsigned number) input line (use 0 for first line) @retval nil requested input or line does not exist @retval table input data: * `name` (string) input line name * `source` (number) input source index * `weight` (number) input weight * `offset` (number) input offset * `switch` (number) input switch index @status current Introduced in 2.0.0, `switch` added in TODO */ static int luaModelGetInput(lua_State *L) { unsigned int chn = luaL_checkunsigned(L, 1); unsigned int idx = luaL_checkunsigned(L, 2); unsigned int first = getFirstInput(chn); unsigned int count = getInputsCountFromFirst(chn, first); if (idx < count) { ExpoData * expo = expoAddress(first+idx); lua_newtable(L); lua_pushtablezstring(L, "name", expo->name); lua_pushtableinteger(L, "source", expo->srcRaw); lua_pushtableinteger(L, "weight", expo->weight); lua_pushtableinteger(L, "offset", expo->offset); lua_pushtableinteger(L, "switch", expo->swtch); } else { lua_pushnil(L); } return 1; }
void deleteExpoMix(uint8_t expo, uint8_t idx) { pauseMixerCalculations(); if (expo) { ExpoData *expo = expoAddress(idx); int input = expo->chn; memmove(expo, expo+1, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); memclear(&g_model.expoData[MAX_EXPOS-1], sizeof(ExpoData)); if (!isInputAvailable(input)) { memclear(&g_model.inputNames[input], LEN_INPUT_NAME); } } else { MixData *mix = mixAddress(idx); memmove(mix, mix+1, (MAX_MIXERS-(idx+1))*sizeof(MixData)); memclear(&g_model.mixData[MAX_MIXERS-1], sizeof(MixData)); } resumeMixerCalculations(); eeDirty(EE_MODEL); }
/*luadoc @function model.insertInput(input, line, value) Inserts an Input at specified line @param input (unsigned number) input number (use 0 for Input1) @param line (unsigned number) input line (use 0 for first line) @param value (table) input data, see model.getInput() @status current Introduced in 2.0.0, `switch` added in TODO */ static int luaModelInsertInput(lua_State *L) { unsigned int chn = luaL_checkunsigned(L, 1); unsigned int idx = luaL_checkunsigned(L, 2); unsigned int first = getFirstInput(chn); unsigned int count = getInputsCountFromFirst(chn, first); if (chn<MAX_INPUTS && getExpoMixCount(1)<MAX_EXPOS && idx<=count) { idx = first + idx; s_currCh = chn + 1; insertExpoMix(1, idx); ExpoData * expo = expoAddress(idx); luaL_checktype(L, -1, LUA_TTABLE); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { luaL_checktype(L, -2, LUA_TSTRING); // key is string const char * key = luaL_checkstring(L, -2); if (!strcmp(key, "name")) { const char * name = luaL_checkstring(L, -1); str2zchar(expo->name, name, sizeof(expo->name)); } else if (!strcmp(key, "source")) { expo->srcRaw = luaL_checkinteger(L, -1); } else if (!strcmp(key, "weight")) { expo->weight = luaL_checkinteger(L, -1); } else if (!strcmp(key, "offset")) { expo->offset = luaL_checkinteger(L, -1); } else if (!strcmp(key, "switch")) { expo->swtch = luaL_checkinteger(L, -1); } } } return 0; }
bool swapExpoMix(uint8_t expo, uint8_t &idx, uint8_t up) { void *x, *y; uint8_t size; int8_t tgt_idx = (up ? idx-1 : idx+1); if (expo) { x = (ExpoData *)expoAddress(idx); if (tgt_idx < 0) { if (((ExpoData *)x)->chn == 0) return false; ((ExpoData *)x)->chn--; return true; } if (tgt_idx == MAX_EXPOS) { if (((ExpoData *)x)->chn == NUM_INPUTS-1) return false; ((ExpoData *)x)->chn++; return true; } y = (ExpoData *)expoAddress(tgt_idx); if(((ExpoData *)x)->chn != ((ExpoData *)y)->chn || !EXPO_VALID((ExpoData *)y)) { if (up) { if (((ExpoData *)x)->chn>0) ((ExpoData *)x)->chn--; else return false; } else { if (((ExpoData *)x)->chn<NUM_INPUTS-1) ((ExpoData *)x)->chn++; else return false; } return true; } size = sizeof(ExpoData); } else { x = (MixData *)mixAddress(idx); if (tgt_idx < 0) { if (((MixData *)x)->destCh == 0) return false; ((MixData *)x)->destCh--; return true; } if (tgt_idx == MAX_MIXERS) { if (((MixData *)x)->destCh == NUM_CHNOUT-1) return false; ((MixData *)x)->destCh++; return true; } y = (MixData *)mixAddress(tgt_idx); uint8_t destCh = ((MixData *)x)->destCh; if(!((MixData *)y)->srcRaw || destCh != ((MixData *)y)->destCh) { if (up) { if (destCh>0) ((MixData *)x)->destCh--; else return false; } else { if (destCh<NUM_CHNOUT-1) ((MixData *)x)->destCh++; else return false; } return true; } size = sizeof(MixData); } pauseMixerCalculations(); memswap(x, y, size); resumeMixerCalculations(); idx = tgt_idx; return true; }
TEST(Trims, invertedThrottlePlusthrottleTrimWithZeroWeightOnThrottle) { MODEL_RESET(); modelDefault(0); g_model.throttleReversed = 1; g_model.thrTrim = 1; #if defined(PCBTARANIS) // the input already exists ExpoData *expo = expoAddress(THR_STICK); #else ExpoData *expo = expoAddress(0); expo->mode = 3; expo->chn = THR_STICK; #endif expo->weight = 0; // stick max + trim max anaInValues[THR_STICK] = +1024; setTrimValue(0, THR_STICK, TRIM_MAX); evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); // stick max + trim mid anaInValues[THR_STICK] = +1024; setTrimValue(0, THR_STICK, 0); evalMixes(1); EXPECT_LE(abs(channelOutputs[2] - 125), 1); // stick max + trim min anaInValues[THR_STICK] = +1024; setTrimValue(0, THR_STICK, TRIM_MIN); evalMixes(1); EXPECT_EQ(channelOutputs[2], 250); // stick min + trim max anaInValues[THR_STICK] = -1024; setTrimValue(0, THR_STICK, TRIM_MAX); evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); // stick min + trim mid anaInValues[THR_STICK] = -1024; setTrimValue(0, THR_STICK, 0); evalMixes(1); EXPECT_LE(abs(channelOutputs[2] - 125), 1); // stick min + trim min anaInValues[THR_STICK] = -1024; setTrimValue(0, THR_STICK, TRIM_MIN); evalMixes(1); EXPECT_EQ(channelOutputs[2], 250); // now some tests with extended Trims g_model.extendedTrims = 1; // trim min + various stick positions = should always be same value setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN); anaInValues[THR_STICK] = -1024; evalMixes(1); EXPECT_EQ(channelOutputs[2], 1000); anaInValues[THR_STICK] = -300; evalMixes(1); EXPECT_EQ(channelOutputs[2], 1000); anaInValues[THR_STICK] = +300; evalMixes(1); EXPECT_EQ(channelOutputs[2], 1000); anaInValues[THR_STICK] = +1024; evalMixes(1); EXPECT_EQ(channelOutputs[2], 1000); // trim max + various stick positions = should always be same value setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX); anaInValues[THR_STICK] = -1024; evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); anaInValues[THR_STICK] = -300; evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); anaInValues[THR_STICK] = +300; evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); anaInValues[THR_STICK] = +1024; evalMixes(1); EXPECT_EQ(channelOutputs[2], 0); }
void applyExpos(int16_t *anas, uint8_t mode APPLY_EXPOS_EXTRA_PARAMS) { #if defined(PCBTARANIS) #if defined(HELI) int16_t heliAnasCopy[4]; memcpy(heliAnasCopy, heliAnas, sizeof(heliAnasCopy)); #endif #else int16_t anas2[NUM_INPUTS]; // values before expo, to ensure same expo base when multiple expo lines are used memcpy(anas2, anas, sizeof(anas2)); #endif int8_t cur_chn = -1; for (uint8_t i=0; i<MAX_EXPOS; i++) { #if defined(BOLD_FONT) if (mode==e_perout_mode_normal) swOn[i].activeExpo = false; #endif ExpoData * ed = expoAddress(i); if (!EXPO_VALID(ed)) break; // end of list if (ed->chn == cur_chn) continue; if (ed->flightModes & (1<<mixerCurrentFlightMode)) continue; if (getSwitch(ed->swtch)) { #if defined(PCBTARANIS) int v; if (ed->srcRaw == ovwrIdx) v = ovwrValue; #if defined(HELI) else if (ed->srcRaw == MIXSRC_Ele) v = heliAnasCopy[ELE_STICK]; else if (ed->srcRaw == MIXSRC_Ail) v = heliAnasCopy[AIL_STICK]; #endif else { v = getValue(ed->srcRaw); if (ed->srcRaw >= MIXSRC_FIRST_TELEM && ed->scale > 0) { v = (v * 1024) / convertTelemValue(ed->srcRaw-MIXSRC_FIRST_TELEM+1, ed->scale); } v = limit(-1024, v, 1024); } #else int16_t v = anas2[ed->chn]; #endif if (EXPO_MODE_ENABLE(ed, v)) { #if defined(BOLD_FONT) if (mode==e_perout_mode_normal) swOn[i].activeExpo = true; #endif cur_chn = ed->chn; //========== CURVE================= #if defined(PCBTARANIS) if (ed->curve.value) { v = applyCurve(v, ed->curve); } #else int8_t curveParam = ed->curveParam; if (curveParam) { if (ed->curveMode == MODE_CURVE) v = applyCurve(v, curveParam); else v = expo(v, GET_GVAR(curveParam, -100, 100, mixerCurrentFlightMode)); } #endif //========== WEIGHT =============== int16_t weight = GET_GVAR(ed->weight, MIN_EXPO_WEIGHT, 100, mixerCurrentFlightMode); weight = calc100to256(weight); v = ((int32_t)v * weight) >> 8; #if defined(PCBTARANIS) //========== OFFSET =============== int16_t offset = GET_GVAR(ed->offset, -100, 100, mixerCurrentFlightMode); if (offset) v += calc100toRESX(offset); //========== TRIMS ================ if (ed->carryTrim < TRIM_ON) virtualInputsTrims[cur_chn] = -ed->carryTrim - 1; else if (ed->carryTrim == TRIM_ON && ed->srcRaw >= MIXSRC_Rud && ed->srcRaw <= MIXSRC_Ail) virtualInputsTrims[cur_chn] = ed->srcRaw - MIXSRC_Rud; else virtualInputsTrims[cur_chn] = -1; #if defined(HELI) if (ed->srcRaw == MIXSRC_Ele) { heliAnas[ELE_STICK] = v; heliTrims[ELE_STICK] = virtualInputsTrims[cur_chn]; } else if (ed->srcRaw == MIXSRC_Ail) { heliAnas[AIL_STICK] = v; heliTrims[AIL_STICK] = virtualInputsTrims[cur_chn]; } #endif #endif anas[cur_chn] = v; } } }
void menuModelExpoMix(uint8_t expo, uint8_t event) { uint8_t sub = m_posVert; if (s_editMode > 0) s_editMode = 0; uint8_t chn = (expo ? expoAddress(s_currIdx)->chn+1 : mixAddress(s_currIdx)->destCh+1); switch (event) { case EVT_ENTRY: case EVT_ENTRY_UP: s_copyMode = 0; s_copyTgtOfs = 0; break; case EVT_KEY_LONG(KEY_EXIT): if (s_copyMode && s_copyTgtOfs == 0) { deleteExpoMix(expo, s_currIdx); killEvents(event); event = 0; } // no break case EVT_KEY_BREAK(KEY_EXIT): if (s_copyMode) { if (s_copyTgtOfs) { // cancel the current copy / move operation if (s_copyMode == COPY_MODE) { deleteExpoMix(expo, s_currIdx); } else { do { swapExpoMix(expo, s_currIdx, s_copyTgtOfs > 0); s_copyTgtOfs += (s_copyTgtOfs < 0 ? +1 : -1); } while (s_copyTgtOfs != 0); eeDirty(EE_MODEL); } m_posVert = s_copySrcRow; s_copyTgtOfs = 0; } s_copyMode = 0; event = 0; } break; case EVT_KEY_BREAK(KEY_ENTER): if ((!s_currCh || (s_copyMode && !s_copyTgtOfs)) && !READ_ONLY()) { s_copyMode = (s_copyMode == COPY_MODE ? MOVE_MODE : COPY_MODE); s_copySrcIdx = s_currIdx; s_copySrcCh = chn; s_copySrcRow = sub; break; } // no break case EVT_KEY_LONG(KEY_ENTER): killEvents(event); if (s_copyTgtOfs) { s_copyMode = 0; s_copyTgtOfs = 0; } else { if (READ_ONLY()) { if (!s_currCh) { pushMenu(expo ? menuModelExpoOne : menuModelMixOne); } } else { if (s_copyMode) s_currCh = 0; if (s_currCh) { if (reachExpoMixCountLimit(expo)) break; insertExpoMix(expo, s_currIdx); pushMenu(expo ? menuModelExpoOne : menuModelMixOne); s_copyMode = 0; } else { event = 0; s_copyMode = 0; MENU_ADD_ITEM(STR_EDIT); MENU_ADD_ITEM(STR_INSERT_BEFORE); MENU_ADD_ITEM(STR_INSERT_AFTER); MENU_ADD_ITEM(STR_COPY); MENU_ADD_ITEM(STR_MOVE); MENU_ADD_ITEM(STR_DELETE); menuHandler = onExpoMixMenu; } } } break; case EVT_KEY_LONG(KEY_LEFT): case EVT_KEY_LONG(KEY_RIGHT): if (s_copyMode && !s_copyTgtOfs) { if (reachExpoMixCountLimit(expo)) break; s_currCh = chn; if (event == EVT_KEY_LONG(KEY_RIGHT)) { s_currIdx++; m_posVert++; } insertExpoMix(expo, s_currIdx); pushMenu(expo ? menuModelExpoOne : menuModelMixOne); s_copyMode = 0; killEvents(event); } break; case EVT_KEY_FIRST(KEY_MOVE_UP): case EVT_KEY_REPT(KEY_MOVE_UP): case EVT_KEY_FIRST(KEY_MOVE_DOWN): case EVT_KEY_REPT(KEY_MOVE_DOWN): if (s_copyMode) { uint8_t key = (event & 0x1f); uint8_t next_ofs = (key==KEY_MOVE_UP ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { // insert a mix on the same channel (just above / just below) if (reachExpoMixCountLimit(expo)) break; copyExpoMix(expo, s_currIdx); if (key==KEY_MOVE_DOWN) s_currIdx++; else if (sub-s_pgOfs >= 6) s_pgOfs++; } else if (next_ofs==0 && s_copyMode==COPY_MODE) { // delete the mix deleteExpoMix(expo, s_currIdx); if (key==KEY_MOVE_UP) s_currIdx--; } else { // only swap the mix with its neighbor if (!swapExpoMix(expo, s_currIdx, key==KEY_MOVE_UP)) break; eeDirty(EE_MODEL); } s_copyTgtOfs = next_ofs; } break; } if (expo) { lcd_outdezAtt(FW*sizeof(TR_MENUINPUTS)+FW+FW/2, 0, getExpoMixCount(true)); lcd_puts(FW*sizeof(TR_MENUINPUTS)+FW+FW/2, 0, STR_MAX(MAX_EXPOS)); // Value uint8_t index = expoAddress(s_currIdx)->chn; if (!s_currCh) { lcd_outdezAtt(127, 2, calcRESXto1000(anas[index]), PREC1|TINSIZE); } SIMPLE_MENU(STR_MENUINPUTS, menuTabModel, e_InputsAll, s_maxLines); // Gauge if (!s_currCh) { drawGauge(127, 1, 58, 6, anas[index], 1024); } } else { lcd_outdezAtt(FW*sizeof(TR_MIXER)+FW+FW/2, 0, getExpoMixCount(false)); lcd_puts(FW*sizeof(TR_MIXER)+FW+FW/2, 0, STR_MAX(MAX_MIXERS)); // Value uint8_t index = mixAddress(s_currIdx)->destCh; if (!s_currCh) { displayHeaderChannelName(index); lcd_outdezAtt(127, 2, calcRESXto1000(ex_chans[index]), PREC1|TINSIZE); } SIMPLE_MENU(STR_MIXER, menuTabModel, e_MixAll, s_maxLines); // Gauge if (!s_currCh) { drawGauge(127, 1, 58, 6, ex_chans[index], 1024); } } sub = m_posVert; s_currCh = 0; int cur = 0; int i = 0; for (int ch=1; ch<=(expo ? NUM_INPUTS : NUM_CHNOUT); ch++) { void *pointer = NULL; MixData * &md = (MixData * &)pointer; ExpoData * &ed = (ExpoData * &)pointer; coord_t y = MENU_HEADER_HEIGHT+1+(cur-s_pgOfs)*FH; if (expo ? (i<MAX_EXPOS && (ed=expoAddress(i))->chn+1 == ch && EXPO_VALID(ed)) : (i<MAX_MIXERS && (md=mixAddress(i))->srcRaw && md->destCh+1 == ch)) { if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { if (expo) { putsMixerSource(0, y, ch, 0); } else { putsChn(0, y, ch, 0); // show CHx } } uint8_t mixCnt = 0; do { if (s_copyMode) { if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && s_copyTgtOfs != 0 && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { lcd_rect(expo ? 18 : 22, y-1, expo ? LCD_W-18 : LCD_W-22, 9, DOTTED); cur++; y+=FH; } if (s_currIdx == i) { sub = m_posVert = cur; s_currCh = ch; } } else if (sub == cur) { s_currIdx = i; } if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { uint8_t attr = ((s_copyMode || sub != cur) ? 0 : INVERS); if (expo) { GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, attr | (isExpoActive(i) ? BOLD : 0), 0, 0); displayExpoLine(y, ed); if (ed->mode!=3) { lcd_putc(EXPO_LINE_SIDE_POS, y, ed->mode == 2 ? 126 : 127); } } else { if (mixCnt > 0) lcd_putsiAtt(FW, y, STR_VMLTPX2, md->mltpx, 0); putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw, 0); gvarWeightItem(MIX_LINE_WEIGHT_POS, y, md, attr | (isMixActive(i) ? BOLD : 0), 0); displayMixLine(y, md); char cs = ' '; if (md->speedDown || md->speedUp) cs = 'S'; if (md->delayUp || md->delayDown) cs = (cs =='S' ? '*' : 'D'); lcd_putc(MIX_LINE_DELAY_POS, y, cs); } if (s_copyMode) { if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { /* draw a border around the raw on selection mode (copy/move) */ lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, s_copyMode == COPY_MODE ? SOLID : DOTTED); } if (cur == sub) { /* invert the raw when it's the current one */ drawFilledRect(expo ? EXPO_LINE_SELECT_POS+1 : 23, y, expo ? (LCD_W-EXPO_LINE_SELECT_POS-2) : (LCD_W-24), 7); } } } cur++; y+=FH; mixCnt++; i++; if (expo) ed++; else md++; } while (expo ? (i<MAX_EXPOS && ed->chn+1 == ch && EXPO_VALID(ed)) : (i<MAX_MIXERS && md->srcRaw && md->destCh+1 == ch)); if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? LCD_W-EXPO_LINE_SELECT_POS : LCD_W-22, 9, DOTTED); cur++; y+=FH; } } else { uint8_t attr = 0; if (sub == cur) { s_currIdx = i; s_currCh = ch; if (!s_copyMode) { attr = INVERS; } } if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { if (expo) { putsMixerSource(0, y, ch, attr); } else { putsChn(0, y, ch, attr); // show CHx } if (s_copyMode == MOVE_MODE && s_copySrcCh == ch) { lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, DOTTED); } } cur++; y+=FH; } } s_maxLines = cur; if (sub >= s_maxLines-1) m_posVert = s_maxLines-1; }
void menuModelExpoOne(uint8_t event) { if (event == EVT_KEY_LONG(KEY_MENU)) { pushMenu(menuChannelsView); killEvents(event); } ExpoData *ed = expoAddress(s_currIdx); putsMixerSource(7*FW+FW/2, 0, MIXSRC_FIRST_INPUT+ed->chn, 0); SUBMENU(STR_MENUINPUTS, EXPO_FIELD_MAX, {0, 0, 0, ed->srcRaw >= MIXSRC_FIRST_TELEM ? (uint8_t)0 : (uint8_t)HIDDEN_ROW, 0, 0, CASE_CURVES(CURVE_ROWS) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/}); SET_SCROLLBAR_X(EXPO_ONE_2ND_COLUMN+10*FW); int8_t sub = m_posVert; coord_t y = MENU_HEADER_HEIGHT + 1; for (unsigned int k=0; k<NUM_BODY_LINES; k++) { int i = k + s_pgOfs; for (int j=0; j<=i; ++j) { if (j<(int)DIM(mstate_tab) && mstate_tab[j] == HIDDEN_ROW) { ++i; } } LcdFlags attr = (sub==i ? (s_editMode>0 ? BLINK|INVERS : INVERS) : 0); switch(i) { case EXPO_FIELD_INPUT_NAME: editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_INPUTNAME, g_model.inputNames[ed->chn], sizeof(g_model.inputNames[ed->chn]), event, attr); break; case EXPO_FIELD_NAME: editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_EXPONAME, ed->name, sizeof(ed->name), event, attr); break; case EXPO_FIELD_SOURCE: lcd_putsLeft(y, NO_INDENT(STR_SOURCE)); putsMixerSource(EXPO_ONE_2ND_COLUMN, y, ed->srcRaw, STREXPANDED|attr); if (attr) ed->srcRaw = checkIncDec(event, ed->srcRaw, INPUTSRC_FIRST, INPUTSRC_LAST, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isInputSourceAvailable); break; case EXPO_FIELD_SCALE: lcd_putsLeft(y, STR_SCALE); putsTelemetryChannelValue(EXPO_ONE_2ND_COLUMN, y, (ed->srcRaw - MIXSRC_FIRST_TELEM)/3, convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale), LEFT|attr); if (attr) ed->scale = checkIncDec(event, ed->scale, 0, maxTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1), EE_MODEL); break; case EXPO_FIELD_WEIGHT: lcd_putsLeft(y, STR_WEIGHT); ed->weight = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->weight, MIN_EXPO_WEIGHT, 100, LEFT|attr, 0, event); break; case EXPO_FIELD_OFFSET: lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); ed->offset = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->offset, -100, 100, LEFT|attr, 0, event); break; #if defined(CURVES) case EXPO_FIELD_CURVE: lcd_putsLeft(y, STR_CURVE); editCurveRef(EXPO_ONE_2ND_COLUMN, y, ed->curve, event, attr); break; #endif #if defined(FLIGHT_MODES) case EXPO_FIELD_FLIGHT_MODES: ed->flightModes = editFlightModes(EXPO_ONE_2ND_COLUMN, y, event, ed->flightModes, attr); break; #endif case EXPO_FIELD_SWITCH: ed->swtch = switchMenuItem(EXPO_ONE_2ND_COLUMN, y, ed->swtch, attr, event); break; case EXPO_FIELD_SIDE: ed->mode = 4 - selectMenuItem(EXPO_ONE_2ND_COLUMN, y, STR_SIDE, STR_VSIDE, 4-ed->mode, 1, 3, attr, event); break; case EXPO_FIELD_TRIM: uint8_t not_stick = (ed->srcRaw > MIXSRC_Ail); int8_t carryTrim = -ed->carryTrim; lcd_putsLeft(y, STR_TRIM); lcd_putsiAtt(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); if (attr) ed->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); break; } y += FH; } DrawFunction(expoFn); int x512 = getValue(ed->srcRaw); if (ed->srcRaw >= MIXSRC_FIRST_TELEM) { putsTelemetryChannelValue(LCD_W-8, 6*FH, (ed->srcRaw - MIXSRC_FIRST_TELEM) / 3, x512, 0); if (ed->scale > 0) x512 = (x512 * 1024) / convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale); } else { lcd_outdezAtt(LCD_W-8, 6*FH, calcRESXto1000(x512), PREC1); } x512 = limit(-1024, x512, 1024); int y512 = expoFn(x512); y512 = limit(-1024, y512, 1024); lcd_outdezAtt(LCD_W-8-6*FW, 1*FH, calcRESXto1000(y512), PREC1); x512 = X0+x512/(RESX/WCHART); y512 = (LCD_H-1) - ((y512+RESX)/2) * (LCD_H-1) / RESX; lcd_vline(x512, y512-3, 3*2+1); lcd_hline(x512-3, y512, 3*2+1); }