From 5bd60f593c6c120959c0b5b2008d7bd00cb1e0c1 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Sat, 25 Sep 2021 11:02:43 +0100 Subject: [PATCH] animate dialogs --- apps/file_manager/folder.cpp | 2 + desktop/gui.cpp | 159 +++++++++++++++++++++-------------- desktop/os.header | 1 + desktop/styles.header | 2 +- res/Theme Source.dat | Bin 53015 -> 52919 bytes res/Themes/Theme.dat | Bin 53636 -> 53592 bytes 6 files changed, 101 insertions(+), 63 deletions(-) diff --git a/apps/file_manager/folder.cpp b/apps/file_manager/folder.cpp index 3ca5015..0d62cfe 100644 --- a/apps/file_manager/folder.cpp +++ b/apps/file_manager/folder.cpp @@ -56,6 +56,8 @@ EsError FSDirRenameItem(Folder *folder, String oldName, String newName) { EsError FSDirEnumerate(Folder *folder) { // TODO Recurse mode. + + EsSleep(2000); EsNodeType type; diff --git a/desktop/gui.cpp b/desktop/gui.cpp index ed3f221..dfb8966 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -85,6 +85,7 @@ EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan); EsElement *UIFindHoverElementRecursively(EsElement *element, int offsetX, int offsetY, EsPoint position); const EsStyle *UIGetDefaultStyleVariant(const EsStyle *style, EsElement *parent); void AccessKeysCenterHint(EsElement *element, EsMessage *message); +void UIRemoveFocusFromElement(EsElement *oldFocus); void InspectorSetup(EsWindow *window); void InspectorNotifyElementEvent(EsElement *element, const char *cCategory, const char *cFormat, ...); @@ -449,7 +450,7 @@ struct EsWindow : EsElement { EsElement *mainPanel, *toolbar; EsPanel *toolbarSwitcher; - EsElement *dialogOverlay, *dialogPanel; + EsElement *dialogWrapper; EsPoint mousePosition; @@ -710,67 +711,6 @@ int ProcessWindowBorderMessage(EsWindow *window, EsMessage *message, EsRectangle return ES_HANDLED; } -// --------------------------------- Dialogs. - -void EsDialogClose(EsWindow *window) { - EsMessageMutexCheck(); - EsAssert(window->hasDialog); // The window does not have an open dialog. - window->dialogPanel->Destroy(); - window->dialogOverlay->Destroy(); // TODO This looks bad if we immediately open a new dialog. But maybe it'll look alright with exiting transitions? - window->dialogOverlay = window->dialogPanel = nullptr; - window->children[0]->children[0]->flags &= ~ES_ELEMENT_BLOCK_FOCUS; - window->children[1]->state &= ~UI_STATE_BLOCK_INTERACTION; - window->hasDialog = false; -} - -EsElement *EsDialogShowAlert(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, {}, 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, 0, "OK"); - EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { EsDialogClose(instance->window); }); - EsElementFocus(button); - } - - return buttonArea; -} - -EsElement *EsDialogShow(EsWindow *window) { - EsAssert(window->windowStyle == ES_WINDOW_NORMAL); // Can only show dialogs on normal windows. - EsAssert(!window->hasDialog); // Cannot nest dialogs. - - EsElement *mainStack = window->children[0]; - mainStack->children[0]->flags |= ES_ELEMENT_BLOCK_FOCUS; - window->children[1]->state |= UI_STATE_BLOCK_INTERACTION; - window->hasDialog = true; - window->dialogOverlay = EsPanelCreate(mainStack, ES_CELL_FILL, ES_STYLE_PANEL_MODAL_OVERLAY); - window->dialogOverlay->cName = "modal overlay"; - window->dialogPanel = EsPanelCreate(mainStack, ES_PANEL_VERTICAL | ES_CELL_CENTER | ES_CELL_SHRINK, ES_STYLE_DIALOG_SHADOW); - window->dialogPanel->cName = "dialog"; - - // EsElementStartTransition(window->dialogOverlay, ES_TRANSITION_FADE_IN, ES_FLAGS_DEFAULT, 3.0f); - // EsElementStartTransition(window->dialogPanel, ES_TRANSITION_FADE_IN, ES_FLAGS_DEFAULT, 3.0f); - - return window->dialogPanel; -} - // --------------------------------- Windows. void UIWindowNeedsUpdate(EsWindow *window) { @@ -1298,6 +1238,10 @@ void EsElementStartTransition(EsElement *element, EsTransitionType transitionTyp return; } + if (~element->state & UI_STATE_ENTERED) { + flags |= ES_ELEMENT_TRANSITION_ENTRANCE; + } + if (transitionType == ES_TRANSITION_FADE_IN) { flags |= ES_ELEMENT_TRANSITION_ENTRANCE; } else if (transitionType == ES_TRANSITION_FADE_OUT) { @@ -1680,6 +1624,9 @@ void ProcessAnimations() { if (element->transitionFlags & ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE) { EsElementSetHidden(element, true); } + + EsMessage m = { .type = ES_MSG_TRANSITION_COMPLETE }; + EsMessageSend(element, &m); } bool backgroundAnimationComplete = ThemeAnimationComplete(&element->animation); @@ -3553,6 +3500,94 @@ EsButton *EsPanelRadioGroupGetChecked(EsPanel *panel) { return (EsButton *) panel->GetChild(0); } +// --------------------------------- Dialogs. + +int ProcessDialogClosingMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_TRANSITION_COMPLETE) { + // Destroy the dialog and its wrapper. + EsElementDestroy(EsElementGetLayoutParent(element)); + } + + return ProcessPanelMessage(element, message); +} + +void EsDialogClose(EsWindow *window) { + EsMessageMutexCheck(); + EsAssert(window->hasDialog); + + EsAssert(window->dialogWrapper->children[0]->messageClass == ProcessPanelMessage); + window->dialogWrapper->children[0]->messageClass = ProcessDialogClosingMessage; + EsElementStartTransition(window->dialogWrapper->children[0], ES_TRANSITION_ZOOM_OUT_LIGHT, ES_ELEMENT_TRANSITION_EXIT, 1.0f); + + window->dialogWrapper = nullptr; + window->children[0]->children[0]->state &= ~UI_STATE_BLOCK_INTERACTION; + window->children[1]->state &= ~UI_STATE_BLOCK_INTERACTION; + window->hasDialog = false; + + if (window->inactiveFocus) { + EsElementFocus(window->inactiveFocus, false); + window->inactiveFocus->Repaint(true); + window->inactiveFocus = nullptr; + } +} + +EsElement *EsDialogShowAlert(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, {}, 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, 0, "OK"); + EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { EsDialogClose(instance->window); }); + EsElementFocus(button); + } + + return buttonArea; +} + +EsElement *EsDialogShow(EsWindow *window) { + // TODO Show on a separate window? + // TODO Maybe allow nested dialogs? + + EsAssert(window->windowStyle == ES_WINDOW_NORMAL); // Can only show dialogs on normal windows. + EsAssert(!window->hasDialog); // Cannot nest dialogs. + + if (window->focused) { + window->inactiveFocus = window->focused; + window->inactiveFocus->Repaint(true); + UIRemoveFocusFromElement(window->focused); + window->focused = nullptr; + } + + EsElement *mainStack = window->children[0]; + mainStack->children[0]->state |= UI_STATE_BLOCK_INTERACTION; // Main content. + window->children[1]->state |= UI_STATE_BLOCK_INTERACTION; // Toolbar. + + window->hasDialog = true; + window->dialogWrapper = EsPanelCreate(mainStack, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_DIALOG_WRAPPER); + window->dialogWrapper->cName = "dialog wrapper"; + EsPanel *dialog = EsPanelCreate(window->dialogWrapper, 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); + + return dialog; +} + // --------------------------------- Canvas panes. struct EsCanvasPane : EsElement { diff --git a/desktop/os.header b/desktop/os.header index 1bd49b3..4b3131a 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -941,6 +941,7 @@ enum EsMessageType { ES_MSG_MOUSE_MIDDLE_DRAG = 0x2028 // Similar to LEFT_DRAG above, but for the middle button. ES_MSG_GET_ACCESS_KEY_HINT_BOUNDS = 0x2029 // Get the bounds to display an access key hint. ES_MSG_UI_SCALE_CHANGED = 0x202A // The UI scale has changed. + ES_MSG_TRANSITION_COMPLETE = 0x202B // The transition started with EsElementStartTransition completed. // State change messages: (causes a style refresh) ES_MSG_STATE_CHANGE_MESSAGE_START = 0x2080 diff --git a/desktop/styles.header b/desktop/styles.header index 0050614..0608bea 100644 --- a/desktop/styles.header +++ b/desktop/styles.header @@ -22,6 +22,7 @@ define ES_STYLE_DIALOG_BUTTON_AREA (ES_STYLE_CAST(1259)) define ES_STYLE_DIALOG_CONTENT (ES_STYLE_CAST(1261)) define ES_STYLE_DIALOG_HEADING (ES_STYLE_CAST(1263)) define ES_STYLE_DIALOG_SHADOW (ES_STYLE_CAST(1311)) +private define ES_STYLE_DIALOG_WRAPPER (ES_STYLE_CAST(1665)) private define ES_STYLE_DOUBLE_CLICK_TEST (ES_STYLE_CAST(1585)) define ES_STYLE_ICON_DISPLAY (ES_STYLE_CAST(1265)) define ES_STYLE_ICON_DISPLAY_SMALL (ES_STYLE_CAST(1543)) @@ -61,7 +62,6 @@ private define ES_STYLE_PANEL_INSPECTOR_WINDOW_ROOT (ES_STYLE_CAST(1319)) private define ES_STYLE_PANEL_MENU_COLUMN (ES_STYLE_CAST(1321)) private define ES_STYLE_PANEL_MENU_CONTAINER (ES_STYLE_CAST(1323)) private define ES_STYLE_PANEL_MENU_ROOT (ES_STYLE_CAST(1325)) -private define ES_STYLE_PANEL_MODAL_OVERLAY (ES_STYLE_CAST(1327)) define ES_STYLE_PANEL_POPUP (ES_STYLE_CAST(1331)) define ES_STYLE_PANEL_SHEET (ES_STYLE_CAST(1333)) private define ES_STYLE_PANEL_SHUTDOWN_OVERLAY (ES_STYLE_CAST(1335)) diff --git a/res/Theme Source.dat b/res/Theme Source.dat index 794062c4d37d37003c597a0560b39b45a43b5642..a35ebe1ccd22437fff2f518bcff94ff8f0ac694a 100644 GIT binary patch delta 297 zcmbO}k9qrCW>zr<1_q~%tTjxU_HDf`nTa|1>3YE#i7ENz{H=_PjgD=7P{Ht`#Dap< zB8N7H%~P0ESvFr~i{#rJAm+u)!V3hG=SjveH#sp+&J!-#TqsqraC6Zf9!5n*`?iMG zr2Gm6BZfzE&y^Wc7*ZM-fi`LYu{IDlOlI6;I{A+n|Kxuw)F;P>u#&EWiP3Q~<3-NSlGYW3LeISixbKp@6X0-_rpE#A4l;r0*7NsVZW?pt` iV}$zJIX|x?HLv9V=2<7MGcjqjO)frduzAnvXl4L$&SUcc delta 281 zcmdl!mwEa;W>zr<1_ry0tTjx+z74%DnTa|1>3YE#i7ENz{H=_eS23xwNH)1Qcs5K5 zNX$#k(euquNzCytOD)PttQ6mThAoDV(S5R@n8aiSb&1Jx!YUk|4ULSAj44f%{S{@H zQW_>pdFk-*0PSFC;sueslMUvWOnxt8y;(!lit`wyhC@OL=p z=j0bD7#f3Z>D~P0h$XWi)GN;Uc_pcNB^8B_HwT}%&csyEI9cbkfy6|xXOR5@)LYTm KvpM>73^M>Ai(K{q diff --git a/res/Themes/Theme.dat b/res/Themes/Theme.dat index 65f8991ac70f8e9b973dd10c0ee13758fd269b0b..8c2faf663a2f81c8ab422e7ae7fe698e3fc4d5a7 100644 GIT binary patch delta 4167 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 t6HGNbzxpBe3+g>aL|zUzY{yE{|{+ln`{68