diff --git a/apps/2048.cpp b/apps/2048.cpp index 8534b01..38f945b 100644 --- a/apps/2048.cpp +++ b/apps/2048.cpp @@ -180,10 +180,10 @@ void SpawnTile() { if (!MoveTiles(-1, 0, true) && !MoveTiles(1, 0, true) && !MoveTiles(0, -1, true) && !MoveTiles(0, 1, true)) { // No moves are possible. if (highScore < score) { - EsDialogShowAlert(instance->window, INTERFACE_STRING(Game2048GameOver), INTERFACE_STRING(Game2048NewHighScore), + EsDialogShow(instance->window, INTERFACE_STRING(Game2048GameOver), INTERFACE_STRING(Game2048NewHighScore), ES_ICON_DIALOG_INFORMATION, ES_DIALOG_ALERT_OK_BUTTON); } else { - EsDialogShowAlert(instance->window, INTERFACE_STRING(Game2048GameOver), INTERFACE_STRING(Game2048GameOverExplanation), + EsDialogShow(instance->window, INTERFACE_STRING(Game2048GameOver), INTERFACE_STRING(Game2048GameOverExplanation), ES_ICON_DIALOG_INFORMATION, ES_DIALOG_ALERT_OK_BUTTON); } } diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp index 4ebe10b..d1cb4de 100644 --- a/apps/file_manager/main.cpp +++ b/apps/file_manager/main.cpp @@ -128,6 +128,7 @@ struct Instance : EsInstance { EsTextbox *breadcrumbBar; EsButton *newFolderButton; EsTextDisplay *status; + EsDialog *blockingDialog; union { struct { @@ -312,7 +313,8 @@ void BlockingTaskThread(EsGeneric _instance) { void BlockingTaskComplete(Instance *instance) { EsAssert(instance->blockingTaskInProgress); // Task should have been in progress. instance->blockingTaskInProgress = false; - if (instance->blockingTaskReachedTimeout) EsDialogClose(instance->window); + if (instance->blockingTaskReachedTimeout) EsDialogClose(instance->blockingDialog); + instance->blockingDialog = nullptr; Task *task = &instance->blockingTask; if (task->then) task->then(instance, task); } @@ -331,7 +333,7 @@ void BlockingTaskQueue(Instance *instance, Task task) { if (result == ES_ERROR_TIMEOUT_REACHED) { instance->blockingTaskReachedTimeout = true; - EsDialogShowAlert(instance->window, task.cDescription, -1, INTERFACE_STRING(FileManagerOngoingTaskDescription), ES_ICON_TOOLS_TIMER_SYMBOLIC); + EsDialogShow(instance->window, task.cDescription, -1, INTERFACE_STRING(FileManagerOngoingTaskDescription), ES_ICON_TOOLS_TIMER_SYMBOLIC); // TODO Progress bar; cancelling tasks. } else { instance->blockingTaskReachedTimeout = false; diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp index 0ee3042..d6caf99 100644 --- a/apps/file_manager/ui.cpp +++ b/apps/file_manager/ui.cpp @@ -799,7 +799,7 @@ int ListCallback(EsElement *element, EsMessage *message) { EsApplicationStart(&request); StringDestroy(&path); } else { - EsDialogShowAlert(instance->window, INTERFACE_STRING(FileManagerOpenFileError), + EsDialogShow(instance->window, INTERFACE_STRING(FileManagerOpenFileError), INTERFACE_STRING(FileManagerNoRegisteredApplicationsForFile), ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } @@ -1100,7 +1100,7 @@ void InstanceReportError(Instance *instance, int error, EsError code) { message = interfaceString_FileManagerPermissionNotGrantedError; } - EsDialogShowAlert(instance->window, errorTypeStrings[error], -1, message, -1, + EsDialogShow(instance->window, errorTypeStrings[error], -1, message, -1, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } diff --git a/apps/irc_client.cpp b/apps/irc_client.cpp index 2f7ab68..2d0137c 100644 --- a/apps/irc_client.cpp +++ b/apps/irc_client.cpp @@ -257,7 +257,7 @@ void NetworkingThread(EsGeneric argument) { EsMessageMutexAcquire(); if (errorMessageBytes) { - EsDialogShowAlert(instance->window, EsLiteral("Connection failed"), errorMessage, errorMessageBytes, + EsDialogShow(instance->window, EsLiteral("Connection failed"), errorMessage, errorMessageBytes, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } diff --git a/apps/test.cpp b/apps/test.cpp index 06ed79a..c6a3543 100644 --- a/apps/test.cpp +++ b/apps/test.cpp @@ -153,7 +153,7 @@ void InitialiseInstance(EsInstance *instance) { EsButtonCreate(panel, ES_BUTTON_CHECKBOX, 0, "Checkbox"); EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Alert 1"), [] (EsInstance *, EsElement *element, EsCommand *) { - EsDialogShowAlert(element->window, "Title", -1, "Content.", -1, ES_ICON_DIALOG_WARNING, ES_DIALOG_ALERT_OK_BUTTON); + EsDialogShow(element->window, "Title", -1, "Content.", -1, ES_ICON_DIALOG_WARNING, ES_DIALOG_ALERT_OK_BUTTON); }); EsPanel *table = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_PANEL_TABLE_H_JUSTIFY); diff --git a/desktop/api.cpp b/desktop/api.cpp index a4411ab..65d2c47 100644 --- a/desktop/api.cpp +++ b/desktop/api.cpp @@ -681,18 +681,10 @@ void InstanceClose(EsInstance *instance) { apiInstance->applicationNameBytes, apiInstance->applicationName); } - // NOTE Duplicated from EsDialogShowAlert. - // TODO Is there a good way to make more modular dialogs? - EsElement *dialog = EsDialogShow(instance->window); - EsPanel *heading = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_DIALOG_HEADING); - EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, 0, ES_ICON_DIALOG_WARNING); - EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2, cTitle); - EsPanel *contentArea = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_VERTICAL, ES_STYLE_DIALOG_CONTENT); - EsTextDisplayCreate(contentArea, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, content, contentBytes); - EsPanel *buttonArea = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE, ES_STYLE_DIALOG_BUTTON_AREA); + EsDialog *dialog = EsDialogShow(instance->window, cTitle, -1, content, contentBytes, ES_ICON_DIALOG_WARNING); if (!apiInstance->startupInformation->filePathBytes) { - EsPanel *row = EsPanelCreate(contentArea, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE); + EsPanel *row = EsPanelCreate(dialog->contentArea, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE); EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(FileCloseNewName)); EsTextbox *textbox = EsTextboxCreate(row); EsInstanceClassEditorSettings *editorSettings = &apiInstance->editorSettings; @@ -702,23 +694,18 @@ void InstanceClose(EsInstance *instance) { apiInstance->fileMenuNameTextbox = textbox; } - EsButton *button; - - button = EsButtonCreate(buttonArea, ES_BUTTON_CANCEL, 0, INTERFACE_STRING(CommonCancel)); - - EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { - EsDialogClose(instance->window); + EsDialogAddButton(dialog, ES_BUTTON_CANCEL, 0, INTERFACE_STRING(CommonCancel), + [] (EsInstance *instance, EsElement *, EsCommand *) { + EsDialogClose(instance->window->dialogs.Last()); }); - button = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(FileCloseWithModificationsDelete)); - - EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { + EsDialogAddButton(dialog, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(FileCloseWithModificationsDelete), + [] (EsInstance *instance, EsElement *, EsCommand *) { EsInstanceDestroy(instance); }); - button = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT, 0, INTERFACE_STRING(FileCloseWithModificationsSave)); - - EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { + EsButton *button = EsDialogAddButton(dialog, ES_BUTTON_DEFAULT, 0, INTERFACE_STRING(FileCloseWithModificationsSave), + [] (EsInstance *instance, EsElement *, EsCommand *) { APIInstance *apiInstance = (APIInstance *) instance->_private; if (apiInstance->startupInformation->filePathBytes) { @@ -1169,7 +1156,7 @@ EsMessage *EsMessageReceive() { } break; } - EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotRename), + EsDialogShow(instance->window, INTERFACE_STRING(FileCannotRename), errorMessage, errorMessageBytes, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } @@ -1313,7 +1300,7 @@ void EsInstanceOpenComplete(EsMessage *message, bool success, const char *errorT if (!success || message->instanceOpen.file->error != ES_SUCCESS) { if (errorTextBytes) { - EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotOpen), + EsDialogShow(instance->window, INTERFACE_STRING(FileCannotOpen), errorText, errorTextBytes, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } else { @@ -1331,7 +1318,7 @@ void EsInstanceOpenComplete(EsMessage *message, bool success, const char *errorT break; } - EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotOpen), + EsDialogShow(instance->window, INTERFACE_STRING(FileCannotOpen), errorMessage, -1, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } @@ -1428,7 +1415,7 @@ void EsInstanceSaveComplete(EsMessage *message, bool success) { } break; } - EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotSave), + EsDialogShow(instance->window, INTERFACE_STRING(FileCannotSave), errorMessage, errorMessageBytes, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); } } diff --git a/desktop/gui.cpp b/desktop/gui.cpp index 80b68db..f084f06 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -453,7 +453,7 @@ struct EsWindow : EsElement { EsElement *mainPanel, *toolbar; EsPanel *toolbarSwitcher; - Array dialogs; + Array dialogs; EsPoint mousePosition; @@ -764,6 +764,7 @@ void UIWindowDestroy(EsWindow *window) { EsSyscall(ES_SYSCALL_WINDOW_CLOSE, window->handle, 0, 0, 0); EsHandleClose(window->handle); window->checkVisible.Free(); + EsAssert(!window->dialogs.Length()); window->dialogs.Free(); window->handle = ES_INVALID_HANDLE; } @@ -3551,6 +3552,12 @@ EsButton *EsPanelRadioGroupGetChecked(EsPanel *panel) { // --------------------------------- Dialogs. +struct EsDialog { + EsElement *mainPanel; + EsElement *buttonArea; + EsElement *contentArea; +}; + int ProcessDialogClosingMessage(EsElement *element, EsMessage *message) { if (message->type == ES_MSG_TRANSITION_COMPLETE) { // Destroy the dialog and its wrapper. @@ -3560,17 +3567,19 @@ int ProcessDialogClosingMessage(EsElement *element, EsMessage *message) { return ProcessPanelMessage(element, message); } -void EsDialogClose(EsWindow *window) { +void EsDialogClose(EsDialog *dialog) { EsMessageMutexCheck(); - EsAssert(window->dialogs.Length()); - EsElement *dialog = window->dialogs.Pop(); + EsWindow *window = dialog->mainPanel->window; + bool isTop = window->dialogs.Last() == dialog; + window->dialogs.FindAndDelete(dialog, true); - EsAssert(dialog->messageClass == ProcessPanelMessage); - dialog->messageClass = ProcessDialogClosingMessage; - EsElementStartTransition(dialog, ES_TRANSITION_ZOOM_OUT_LIGHT, ES_ELEMENT_TRANSITION_EXIT, 1.0f); + EsAssert(dialog->mainPanel->messageClass == ProcessPanelMessage); + dialog->mainPanel->messageClass = ProcessDialogClosingMessage; + EsElementStartTransition(dialog->mainPanel, ES_TRANSITION_ZOOM_OUT_LIGHT, ES_ELEMENT_TRANSITION_EXIT, 1.0f); - if (!window->dialogs.Length()) { + if (!isTop) { + } else if (!window->dialogs.Length()) { window->children[0]->children[0]->state &= ~UI_STATE_BLOCK_INTERACTION; window->children[1]->state &= ~UI_STATE_BLOCK_INTERACTION; @@ -3580,43 +3589,18 @@ void EsDialogClose(EsWindow *window) { window->inactiveFocus = nullptr; } } else { - window->dialogs.Last()->state &= ~UI_STATE_BLOCK_INTERACTION; + window->dialogs.Last()->mainPanel->state &= ~UI_STATE_BLOCK_INTERACTION; } } -EsElement *EsDialogShowAlert(EsWindow *window, const char *title, ptrdiff_t titleBytes, +EsDialog *EsDialogShow(EsWindow *window, const char *title, ptrdiff_t titleBytes, const char *content, ptrdiff_t contentBytes, uint32_t iconID, uint32_t flags) { - EsElement *dialog = EsDialogShow(window); - if (!dialog) return nullptr; - EsPanel *heading = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_DIALOG_HEADING); - if (!heading) return nullptr; - - if (iconID) { - EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, 0, iconID); - } - - EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2, - title, titleBytes)->cName = "dialog heading"; - EsTextDisplayCreate(EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_VERTICAL, ES_STYLE_DIALOG_CONTENT), - ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, - content, contentBytes)->cName = "dialog contents"; - - EsPanel *buttonArea = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE, ES_STYLE_DIALOG_BUTTON_AREA); - if (!buttonArea) return nullptr; - - if (flags & ES_DIALOG_ALERT_OK_BUTTON) { - EsButton *button = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT | ES_BUTTON_CANCEL, 0, INTERFACE_STRING(CommonOK)); - EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { EsDialogClose(instance->window); }); - EsElementFocus(button); - } - - return buttonArea; -} - -EsElement *EsDialogShow(EsWindow *window) { // TODO Show on a separate window? // TODO Support dialogs owned by other processes. + EsDialog *dialog = (EsDialog *) EsHeapAllocate(sizeof(EsDialog), true); + if (!dialog) return nullptr; + EsAssert(window->windowStyle == ES_WINDOW_NORMAL); // Can only show dialogs on normal windows. if (window->focused) { @@ -3632,19 +3616,49 @@ EsElement *EsDialogShow(EsWindow *window) { mainStack->children[0]->state |= UI_STATE_BLOCK_INTERACTION; // Main content. window->children[1]->state |= UI_STATE_BLOCK_INTERACTION; // Toolbar. } else { - window->dialogs.Last()->state |= UI_STATE_BLOCK_INTERACTION; + window->dialogs.Last()->mainPanel->state |= UI_STATE_BLOCK_INTERACTION; } EsElement *wrapper = EsPanelCreate(mainStack, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_DIALOG_WRAPPER); wrapper->cName = "dialog wrapper"; - EsPanel *dialog = EsPanelCreate(wrapper, ES_PANEL_VERTICAL | ES_CELL_SHRINK, ES_STYLE_DIALOG_SHADOW); - dialog->cName = "dialog"; - EsElementStartTransition(dialog, ES_TRANSITION_ZOOM_OUT_LIGHT, ES_FLAGS_DEFAULT, 1.0f); + dialog->mainPanel = EsPanelCreate(wrapper, ES_PANEL_VERTICAL | ES_CELL_SHRINK, ES_STYLE_DIALOG_SHADOW); + dialog->mainPanel->cName = "dialog"; + EsElementStartTransition(dialog->mainPanel, ES_TRANSITION_ZOOM_OUT_LIGHT, ES_FLAGS_DEFAULT, 1.0f); window->dialogs.Add(dialog); + EsElement *mainPanel = dialog->mainPanel; + EsPanel *heading = EsPanelCreate(mainPanel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_DIALOG_HEADING); + + if (iconID) { + EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, 0, iconID); + } + + EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2, title, titleBytes)->cName = "dialog heading"; + + dialog->contentArea = EsPanelCreate(mainPanel, ES_CELL_H_FILL | ES_PANEL_VERTICAL, ES_STYLE_DIALOG_CONTENT); + EsTextDisplayCreate(dialog->contentArea, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, content, contentBytes)->cName = "dialog contents"; + + EsPanel *buttonArea = EsPanelCreate(mainPanel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE, ES_STYLE_DIALOG_BUTTON_AREA); + dialog->buttonArea = buttonArea; + + if (flags & ES_DIALOG_ALERT_OK_BUTTON) { + EsButton *button = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT | ES_BUTTON_CANCEL, 0, INTERFACE_STRING(CommonOK)); + EsElementFocus(button); + + EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { + EsDialogClose(instance->window->dialogs.Last()); + }); + } + return dialog; } +EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags, EsStyle *style, const char *label, ptrdiff_t labelBytes, EsCommandCallback callback) { + EsButton *button = EsButtonCreate(dialog->buttonArea, flags, style, label, labelBytes); + if (button) EsButtonOnCommand(button, callback); + return button; +} + // --------------------------------- Canvas panes. struct EsCanvasPane : EsElement { diff --git a/desktop/os.header b/desktop/os.header index d0036db..1f4ae6b 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -21,6 +21,7 @@ opaque_type EsHeap none; opaque_type EsFileStore none; opaque_type EsUserTask none; opaque_type EsBundle none; +opaque_type EsDialog none; type_name uint8_t EsNodeType; type_name intptr_t EsError; @@ -2379,9 +2380,10 @@ function void EsMenuShow(EsMenu *menu, int fixedWidth = 0, int fixedHeight = 0); function void EsMenuCloseAll(); function void EsMenuAddCommandsFromToolbar(EsMenu *menu, EsElement *element); -function void EsDialogClose(EsWindow *window); -function EsElement *EsDialogShow(EsWindow *window); -function EsElement *EsDialogShowAlert(EsWindow *window, STRING title, STRING content, uint32_t iconID, uint32_t flags = ES_FLAGS_DEFAULT); // Returns the button container. +function void EsDialogClose(EsDialog *dialog); +function EsDialog *EsDialogShow(EsWindow *window, STRING title, STRING content, uint32_t iconID, uint32_t flags = ES_FLAGS_DEFAULT); +function EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags = ES_FLAGS_DEFAULT, EsStyle *style = ES_NULL, + STRING label = BLANK_STRING, EsCommandCallback callback = ES_NULL); function void EsToolbarAddFileMenu(EsElement *element, const EsFileMenuSettings *settings = ES_NULL); diff --git a/util/api_table.ini b/util/api_table.ini index 9e2b994..eed8101 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -17,8 +17,7 @@ EsCommandRegister=15 EsCommandSetCallback=16 EsCommandSetDisabled=17 EsDialogClose=18 -EsDialogShow=19 -EsDialogShowAlert=20 +EsDialogAddButton=19 EsCRTrand=21 EsCRTsnprintf=22 EsCRTsprintf=23 @@ -461,3 +460,4 @@ EsBufferWriteInt8=459 EsInstanceGetStartupRequest=460 EsImageDisplayPaint=461 EsElementGetInsetBounds=462 +EsDialogShow=463