39#pragma GCC optimize ("O2")
49#define B_CANCEL ((int)(InputResult::Cancel) - 1)
50#define B_OK ((int)(InputResult::Enter) - 1)
59 m_backgroundColor(
RGB888(64, 64, 64)),
73void InputBox::begin(
char const * modeline,
int viewPortWidth,
int viewPortHeight,
int displayColors)
76 if (displayColors <= 2)
78 else if (displayColors <= 4)
80 else if (displayColors <= 8)
84 m_dispCtrl = m_vgaCtrl;
86 m_vgaCtrl->setResolution(modeline ? modeline :
VESA_640x480_75Hz, viewPortWidth, viewPortHeight);
89 if (!PS2Controller::initialized())
98 m_dispCtrl = displayController;
114 m_buttonText[index] = text;
115 m_buttonSubItems[index] = subItems;
116 m_buttonSubItemsHeight[index] = subItemsHeight;
120void InputBox::resetButtons()
122 for (
int i = 0; i < InputForm::BUTTONS; ++i) {
123 m_buttonText[i] =
nullptr;
124 m_buttonSubItems[i] =
nullptr;
129void InputBox::exec(InputForm * form)
132 form->init(m_existingApp,
true);
141 m_buttonSubItem = form->buttonSubItem;
142 m_lastResult = form->retval;
146InputResult InputBox::textInput(
char const * titleText,
char const * labelText,
char * inOutString,
int maxLength,
char const * buttonCancelText,
char const * buttonOKText,
bool passwordMode)
151 TextInputForm form(
this);
152 form.titleText = titleText;
153 form.labelText = labelText;
154 form.inOutString = inOutString;
155 form.maxLength = maxLength;
156 form.passwordMode = passwordMode;
157 form.autoOK = m_autoOK;
169 MessageForm form(
this);
170 form.titleText = titleText;
171 form.messageText = messageText;
172 form.autoOK = m_autoOK;
183 va_start(ap, format);
184 int size = vsnprintf(
nullptr, 0, format, ap) + 1;
187 va_start(ap, format);
189 vsnprintf(buf, size, format, ap);
190 r =
message(titleText, buf, buttonCancelText, buttonOKText);
197int InputBox::select(
char const * titleText,
char const * messageText,
char const * itemsText,
char separator,
char const * buttonCancelText,
char const * buttonOKText)
202 SelectForm form(
this);
203 form.titleText = titleText;
204 form.messageText = messageText;
205 form.items = itemsText;
206 form.separator = separator;
207 form.itemsList =
nullptr;
208 form.menuMode =
false;
209 form.autoOK = m_autoOK;
212 return form.outSelected;
216InputResult InputBox::select(
char const * titleText,
char const * messageText, StringList * items,
char const * buttonCancelText,
char const * buttonOKText)
221 SelectForm form(
this);
222 form.titleText = titleText;
223 form.messageText = messageText;
224 form.items =
nullptr;
226 form.itemsList = items;
227 form.menuMode =
false;
228 form.autoOK = m_autoOK;
235int InputBox::menu(
char const * titleText,
char const * messageText,
char const * itemsText,
char separator)
237 SelectForm form(
this);
238 form.titleText = titleText;
239 form.messageText = messageText;
240 form.items = itemsText;
241 form.separator = separator;
242 form.itemsList =
nullptr;
243 form.menuMode =
true;
247 return form.outSelected;
251int InputBox::menu(
char const * titleText,
char const * messageText, StringList * items)
253 SelectForm form(
this);
254 form.titleText = titleText;
255 form.messageText = messageText;
256 form.items =
nullptr;
258 form.itemsList = items;
259 form.menuMode =
true;
263 return items->getFirstSelected();
267InputResult InputBox::progressBoxImpl(ProgressForm & form,
char const * titleText,
char const * buttonCancelText,
bool hasProgressBar,
int width)
271 form.titleText = titleText;
272 form.hasProgressBar = hasProgressBar;
285 FileBrowserForm form(
this);
286 form.titleText = titleText;
288 form.directory = directory;
295InputResult InputBox::fileSelector(
char const * titleText,
char const * messageText,
char * inOutDirectory,
int maxDirectoryLength,
char * inOutFilename,
int maxFilenameLength,
char const * buttonCancelText,
char const * buttonOKText)
300 FileSelectorForm form(
this);
301 form.titleText = titleText;
302 form.labelText = messageText;
303 form.inOutDirectory = inOutDirectory;
304 form.maxDirectoryLength = maxDirectoryLength;
305 form.inOutFilename = inOutFilename;
306 form.maxFilenameLength = maxFilenameLength;
319void InputForm::init(
uiApp * app_,
bool modalDialog_)
324 modalDialog = modalDialog_;
328 app->rootWindow()->onPaint = [&]() {
329 inputBox->onPaint(app->canvas());
335 const int titleHeight = titleText && strlen(titleText) ? font->height : 0;
337 constexpr int buttonsSpace = 10;
339 int buttonsWidth = inputBox->minButtonsWidth();
342 for (
int i = 0; i < BUTTONS; ++i) {
343 auto btext = inputBox->buttonText(i);
345 int buttonExtent = app->canvas()->textExtent(font, btext) + 10;
346 buttonsWidth = imax(buttonsWidth, buttonExtent);
351 const int buttonsHeight = totButtons ? font->height + 6 : 0;
353 requiredWidth = buttonsWidth * totButtons + (2 * buttonsSpace) * totButtons;
354 requiredHeight = buttonsHeight + titleHeight + font->height * 2 + 5;
358 requiredWidth = imin(requiredWidth, app->canvas()->getWidth());
360 controlToFocus =
nullptr;
362 mainFrame =
new uiFrame(app->rootWindow(), titleText, UIWINDOW_PARENTCENTER, Size(requiredWidth, requiredHeight),
false);
363 mainFrame->frameProps().resizeable =
false;
364 mainFrame->frameProps().hasMaximizeButton =
false;
365 mainFrame->frameProps().hasMinimizeButton =
false;
366 mainFrame->frameProps().hasCloseButton =
false;
367 mainFrame->onShow = [&]() {
369 app->setFocusedWindow(controlToFocus);
373 autoOKLabel =
nullptr;
379 int panelHeight = buttonsHeight + 10;
380 panel =
new uiPanel(mainFrame, Point(mainFrame->clientPos().X - 1, mainFrame->clientPos().Y + mainFrame->clientSize().height - panelHeight), Size(mainFrame->clientSize().width + 2, panelHeight));
381 panel->windowStyle().borderColor = RGB888(128, 128, 128);
382 panel->panelStyle().backgroundColor = mainFrame->frameStyle().backgroundColor;
383 panel->anchors().top =
false;
384 panel->anchors().bottom =
true;
385 panel->anchors().right =
true;
389 int y = (panelHeight - buttonsHeight) / 2;
390 int x = panel->clientSize().width - buttonsWidth * totButtons - buttonsSpace * (totButtons - 1) - buttonsSpace / 2;
392 for (
int i = 0; i < BUTTONS; ++i)
393 if (inputBox->buttonText(i)) {
395 if (inputBox->buttonSubItems(i)) {
396 auto splitButton =
new uiSplitButton(panel, inputBox->buttonText(i), Point(x, y), Size(buttonsWidth, buttonsHeight), inputBox->buttonsSubItemsHeight(i), inputBox->buttonSubItems(i));
397 splitButton->onSelect = [&, i](
int idx) {
404 auto button =
new uiButton(panel, inputBox->buttonText(i), Point(x, y), Size(buttonsWidth, buttonsHeight));
405 button->onClick = [&, i]() {
411 ctrl->anchors().left =
false;
412 ctrl->anchors().right =
true;
413 x += buttonsWidth + buttonsSpace;
414 controlToFocus = ctrl;
418 autoOKLabel =
new uiLabel(panel,
"", Point(4, y + 2));
420 mainFrame->onTimer = [&](uiTimerHandle t) {
421 int now = esp_timer_get_time() / 1000;
422 if (app->lastUserActionTime() + 900 > now) {
424 app->destroyWindow(autoOKLabel);
433 autoOKLabel->setTextFmt(
"%d", autoOK);
435 app->setTimer(mainFrame, 1000);
446 app->showWindow(mainFrame,
true);
447 app->setActiveWindow(mainFrame);
470void InputForm::doExit(
int value)
473 mainFrame->exitModal(value);
477 app->rootWindow()->frameProps().fillBackground =
false;
486void TextInputForm::calcRequiredSize()
488 labelExtent = app->canvas()->textExtent(font, labelText);
489 editExtent = imin(maxLength * app->canvas()->textExtent(font,
"M") + 15, app->rootWindow()->clientSize().width - labelExtent);
490 requiredWidth = imax(requiredWidth, editExtent + labelExtent + 10);
491 requiredHeight += font->height;
495void TextInputForm::addControls()
497 mainFrame->frameProps().resizeable =
true;
498 mainFrame->frameProps().hasMaximizeButton =
true;
500 const Point clientPos = mainFrame->clientPos();
502 int x = clientPos.X + 4;
503 int y = clientPos.Y + 8;
505 new uiLabel(mainFrame, labelText, Point(x, y));
507 edit =
new uiTextEdit(mainFrame, inOutString, Point(x + labelExtent + 5, y - 4), Size(editExtent - 15, font->height + 6));
508 edit->anchors().right =
true;
509 edit->textEditProps().passwordMode = passwordMode;
510 edit->onKeyType = [&](
uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
512 controlToFocus = edit;
516void TextInputForm::finalize()
519 int len = imin(maxLength, strlen(edit->text()));
520 memcpy(inOutString, edit->text(), len);
521 inOutString[len] = 0;
532void MessageForm::calcRequiredSize()
534 messageExtent = app->canvas()->textExtent(font, messageText);
535 requiredWidth = imax(requiredWidth, messageExtent + 20);
536 requiredHeight += font->height;
540void MessageForm::addControls()
542 int x = mainFrame->clientPos().X + (mainFrame->clientSize().width - messageExtent) / 2;
543 int y = mainFrame->clientPos().Y + 6;
545 new uiLabel(mainFrame, messageText, Point(x, y));
547 mainFrame->onKeyUp = [&](
uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
551void MessageForm::finalize()
562void SelectForm::calcRequiredSize()
564 auto messageExtent = app->canvas()->textExtent(font, messageText);
565 requiredWidth = imax(requiredWidth, messageExtent + 20);
568 requiredHeight += font->height;
572 auto itemsCount = countItems(&maxLength);
573 listBoxHeight = 16 * itemsCount + 2;
574 int requiredHeightUnCut = requiredHeight + listBoxHeight;
575 requiredHeight = imin(requiredHeightUnCut, app->canvas()->getHeight());
576 requiredWidth = imax(requiredWidth, maxLength * app->canvas()->textExtent(font,
"M"));
577 if (requiredHeightUnCut > requiredHeight)
578 listBoxHeight -= requiredHeightUnCut - requiredHeight;
582void SelectForm::addControls()
584 mainFrame->frameProps().resizeable =
true;
585 mainFrame->frameProps().hasMaximizeButton =
true;
587 int x = mainFrame->clientPos().X + 4;
588 int y = mainFrame->clientPos().Y + 6;
590 new uiLabel(mainFrame, messageText, Point(x, y));
592 y += font->height + 6;
594 listBox =
new uiListBox(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - 10, listBoxHeight));
595 listBox->anchors().right =
true;
596 listBox->anchors().bottom =
true;
598 listBox->items().appendSepList(items, separator);
600 listBox->items().copyFrom(*itemsList);
601 listBox->items().copySelectionMapFrom(*itemsList);
604 listBox->listBoxProps().allowMultiSelect =
false;
605 listBox->listBoxProps().selectOnMouseOver =
true;
606 listBox->onClick = [&]() {
611 listBox->onDblClick = [&]() {
616 listBox->onKeyType = [&](
uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
618 controlToFocus = listBox;
622void SelectForm::finalize()
628 itemsList->deselectAll();
630 itemsList->copySelectionMapFrom(listBox->items());
636int SelectForm::countItems(
size_t * maxLength)
641 char const * start = items;
643 auto end = strchr(start, separator);
645 end = strchr(start, 0);
646 int len = end - start;
647 *maxLength = imax(*maxLength, len);
648 start += len + (*end == 0 ? 0 : 1);
651 }
else if (itemsList) {
652 for (
int i = 0; i < itemsList->count(); ++i)
653 *maxLength = imax(*maxLength, strlen(itemsList->get(i)));
654 count += itemsList->count();
665void ProgressForm::calcRequiredSize()
667 requiredWidth = imax(requiredWidth,
width);
668 requiredHeight += font->height + (hasProgressBar ? progressBarHeight : 0);
672void ProgressForm::addControls()
674 int x = mainFrame->clientPos().X + 4;
675 int y = mainFrame->clientPos().Y + 6;
677 label =
new uiLabel(mainFrame,
"", Point(x, y));
679 if (hasProgressBar) {
680 y += font->height + 4;
681 progressBar =
new uiProgressBar(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - 8, font->height));
684 mainFrame->onKeyUp = [&](
uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
688void ProgressForm::show()
698bool ProgressForm::update(
int percentage,
char const * format, ...)
701 progressBar->setPercentage(percentage);
704 va_start(ap, format);
705 int size = vsnprintf(
nullptr, 0, format, ap) + 1;
708 va_start(ap, format);
710 vsnprintf(buf, size, format, ap);
715 app->processEvents();
725void FileBrowserForm::calcRequiredSize()
727 requiredWidth = imax(requiredWidth, BROWSER_WIDTH + CTRLS_DIST + SIDE_BUTTONS_WIDTH);
728 requiredHeight = imax(requiredHeight, BROWSER_HEIGHT);
732void FileBrowserForm::addControls()
734 mainFrame->frameProps().resizeable =
true;
735 mainFrame->frameProps().hasMaximizeButton =
true;
737 mainFrame->onKeyUp = [&](
uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
739 int x = mainFrame->clientPos().X + CTRLS_DIST;
740 int y = mainFrame->clientPos().Y + CTRLS_DIST;
742 fileBrowser =
new uiFileBrowser(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - x - CTRLS_DIST - SIDE_BUTTONS_WIDTH, mainFrame->clientSize().height - panel->size().height - CTRLS_DIST * 2));
743 fileBrowser->anchors().right =
true;
744 fileBrowser->anchors().bottom =
true;
745 fileBrowser->setDirectory(directory);
747 x += fileBrowser->size().width + CTRLS_DIST;
749 newFolderButton =
new uiButton(mainFrame,
"New Folder", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
750 newFolderButton->anchors().left =
false;
751 newFolderButton->anchors().right =
true;
752 newFolderButton->onClick = [&]() {
753 unique_ptr<char[]> dirname(
new char[MAXNAME + 1] { 0 } );
755 fileBrowser->content().makeDirectory(dirname.get());
756 fileBrowser->update();
760 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
762 renameButton =
new uiButton(mainFrame,
"Rename", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
763 renameButton->anchors().left =
false;
764 renameButton->anchors().right =
true;
765 renameButton->onClick = [&]() {
766 if (strcmp(fileBrowser->filename(),
"..") != 0) {
767 int maxlen = fabgl::imax(MAXNAME, strlen(fileBrowser->filename()));
768 unique_ptr<char[]> filename(
new char[MAXNAME + 1] { 0 } );
769 strcpy(filename.get(), fileBrowser->filename());
771 fileBrowser->content().rename(fileBrowser->filename(), filename.get());
772 fileBrowser->update();
777 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
779 deleteButton =
new uiButton(mainFrame,
"Delete", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
780 deleteButton->anchors().left =
false;
781 deleteButton->anchors().right =
true;
782 deleteButton->onClick = [&]() {
783 if (strcmp(fileBrowser->filename(),
"..") != 0) {
785 fileBrowser->content().remove( fileBrowser->filename() );
786 fileBrowser->update();
791 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
793 copyButton =
new uiButton(mainFrame,
"Copy", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
794 copyButton->anchors().left =
false;
795 copyButton->anchors().right =
true;
796 copyButton->onClick = [&]() { doCopy(); };
798 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
800 pasteButton =
new uiButton(mainFrame,
"Paste", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
801 pasteButton->anchors().left =
false;
802 pasteButton->anchors().right =
true;
803 pasteButton->onClick = [&]() { doPaste(); };
804 app->showWindow(pasteButton,
false);
809void FileBrowserForm::finalize()
815void FileBrowserForm::doCopy()
817 if (!fileBrowser->isDirectory()) {
822 srcDirectory = strdup(fileBrowser->directory());
823 srcFilename = strdup(fileBrowser->filename());
824 app->showWindow(pasteButton,
true);
829void FileBrowserForm::doPaste()
831 if (strcmp(srcDirectory, fileBrowser->content().directory()) == 0) {
835 FileBrowser fb_src(srcDirectory);
836 auto fileSize = fb_src.fileSize(srcFilename);
837 auto src = fb_src.openFile(srcFilename,
"rb");
842 if (fileBrowser->content().exists(srcFilename,
false)) {
846 auto dst = fileBrowser->content().openFile(srcFilename,
"wb");
848 auto bytesToCopy = fileSize;
851 ib.progressBox(
"Copying",
"Abort",
true, app->canvas()->getWidth() * 2 / 3, [&](fabgl::ProgressForm * form) {
852 constexpr int BUFLEN = 4096;
853 unique_ptr<uint8_t[]> buf(new uint8_t[BUFLEN]);
854 while (bytesToCopy > 0) {
855 auto r = fread(buf.get(), 1, imin(BUFLEN, bytesToCopy), src);
856 fwrite(buf.get(), 1, r, dst);
860 if (!form->update((int)((double)(fileSize - bytesToCopy) / fileSize * 100),
"Writing %s (%d / %d bytes)", srcFilename, (fileSize - bytesToCopy), fileSize))
867 if (bytesToCopy > 0) {
868 fileBrowser->content().remove(srcFilename);
871 fileBrowser->update();
881void FileSelectorForm::calcRequiredSize()
883 labelExtent = app->canvas()->textExtent(font, labelText);
884 editExtent = imin(maxFilenameLength * app->canvas()->textExtent(font,
"M") + 15, app->rootWindow()->clientSize().width - labelExtent);
885 requiredWidth = imax(requiredWidth, imax(BROWSER_WIDTH, labelExtent + CTRLS_DIST + MINIMUM_EDIT_WIDTH) + CTRLS_DIST);
886 requiredHeight += font->height + CTRLS_DIST + BROWSER_HEIGHT;
890void FileSelectorForm::addControls()
892 mainFrame->frameProps().resizeable =
true;
893 mainFrame->frameProps().hasMaximizeButton =
true;
895 mainFrame->onKeyUp = [&](
uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
897 int x = mainFrame->clientPos().X + CTRLS_DIST;
898 int y = mainFrame->clientPos().Y + CTRLS_DIST;
900 new uiLabel(mainFrame, labelText, Point(x, y + 4));
902 edit =
new uiTextEdit(mainFrame, inOutFilename, Point(x + labelExtent + CTRLS_DIST, y), Size(mainFrame->clientSize().width - labelExtent - x - CTRLS_DIST - 1, font->height + 6));
903 edit->anchors().right =
true;
905 y += edit->size().height + CTRLS_DIST;
907 fileBrowser =
new uiFileBrowser(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - x - 1, mainFrame->clientSize().height - panel->size().height - y + CTRLS_DIST * 2 ));
908 fileBrowser->anchors().right =
true;
909 fileBrowser->anchors().bottom =
true;
910 fileBrowser->setDirectory(inOutDirectory);
911 fileBrowser->onChange = [&]() {
912 if (!fileBrowser->isDirectory()) {
913 edit->setText(fileBrowser->filename());
917 fileBrowser->onDblClick = [&]() {
918 if (!fileBrowser->isDirectory()) {
919 retval = InputResult::Enter;
923 fileBrowser->onKeyType = [&](
uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
925 controlToFocus = edit;
929void FileSelectorForm::finalize()
931 if (retval == InputResult::Enter) {
933 int len = imin(maxFilenameLength, strlen(edit->text()));
934 memcpy(inOutFilename, edit->text(), len);
935 inOutFilename[len] = 0;
937 len = imin(maxDirectoryLength, strlen(fileBrowser->directory()));
938 memcpy(inOutDirectory, fileBrowser->directory(), len);
939 inOutDirectory[len] = 0;
Represents the base abstract class for bitmapped display controllers.
void enableVirtualKeys(bool generateVirtualKeys, bool createVKQueue)
Dynamically enables or disables Virtual Keys generation.
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
static void begin(gpio_num_t port0_clkGPIO, gpio_num_t port0_datGPIO, gpio_num_t port1_clkGPIO=GPIO_UNUSED, gpio_num_t port1_datGPIO=GPIO_UNUSED)
Initializes PS2 device controller.
Represents the VGA 16 colors bitmapped controller.
Represents the VGA 2 colors bitmapped controller.
Represents the VGA 4 colors bitmapped controller.
Represents the VGA 8 colors bitmapped controller.
void destroyWindow(uiWindow *window)
Destroys a window.
int showModalWindow(uiWindow *window)
Makes a window visible and handles it has a modal window.
uiFrame * rootWindow()
Gets a pointer to the root window.
Represents the whole application base class.
Shows and navigates Virtual Filesystem content.
uiFrameStyle & frameStyle()
Sets or gets frame style.
A frame is a window with a title bar, maximize/minimize/close buttons and that is resizeable or movea...
A label is a static text UI element.
Shows a list of selectable string items.
A panel is used to contain and to group some controls.
A progress bar shows progress percentage using a colored bar.
Represents a text edit control.
#define VESA_640x480_75Hz
InputResult
Result of InputBox dialogs helper class.
@ KeyboardPort0_MousePort1
This file contains fabgl::Keyboard definition.
Represents a 24 bit RGB color.
Contains details about the key event.
This file contains fabgl::VGA16Controller definition.
This file contains fabgl::VGA2Controller definition.
This file contains fabgl::VGA4Controller definition.
This file contains fabgl::VGA8Controller definition.