improve tasks button

This commit is contained in:
nakst 2021-09-04 12:02:24 +01:00
parent ecdce9e720
commit 6bdb99a79b
10 changed files with 337 additions and 66 deletions

View File

@ -164,7 +164,60 @@ void CommandCopy(Instance *instance, EsElement *, EsCommand *) {
CommandCopyOrCut(instance, ES_FLAGS_DEFAULT);
}
EsError CommandPasteFile(String source, String destinationBase, void **copyBuffer, bool move, String *_destination) {
struct PasteOperation {
String source, destination;
};
struct PasteTask {
// Input:
String destinationBase;
bool move;
char *pathList;
size_t pathListBytes;
// State:
bool progressByData;
EsFileOffset totalDataToProcess, totalDataProcessed;
size_t sourceItemCount, sourceItemsProcessed;
EsUserTask *userTask;
EsFileOffset lastBytesCopied, cumulativeSecondBytesCopied;
EsFileOffsetDifference bytesPerSecond;
double cumulativeSecondTimeStampMs;
};
bool CommandPasteCopyCallback(EsFileOffset bytesCopied, EsFileOffset totalBytes, EsGeneric data) {
(void) totalBytes;
PasteTask *task = (PasteTask *) data.p;
EsFileOffset delta = bytesCopied - task->lastBytesCopied;
task->totalDataProcessed += delta;
task->lastBytesCopied = bytesCopied;
if (task->progressByData) {
double timeStampMs = EsTimeStampMs();
if (!task->cumulativeSecondTimeStampMs) {
task->cumulativeSecondTimeStampMs = timeStampMs;
task->bytesPerSecond = -1;
} else if (timeStampMs - task->cumulativeSecondTimeStampMs > 1000.0) {
// TODO Test that this calculation is correct.
task->bytesPerSecond = task->cumulativeSecondBytesCopied / (timeStampMs - task->cumulativeSecondTimeStampMs);
task->cumulativeSecondTimeStampMs = timeStampMs;
task->cumulativeSecondBytesCopied = 0;
}
task->cumulativeSecondBytesCopied += delta;
EsUserTaskSetProgress(task->userTask, (double) task->totalDataProcessed / task->totalDataToProcess, task->bytesPerSecond);
}
return EsUserTaskIsRunning(task->userTask);
}
EsError CommandPasteFile(String source, String destinationBase, void **copyBuffer, PasteTask *task, String *_destination) {
if (!EsUserTaskIsRunning(task->userTask)) {
return ES_ERROR_CANCELLED;
}
if (PathHasPrefix(destinationBase, source)) {
return ES_ERROR_TARGET_WITHIN_SOURCE;
}
@ -174,7 +227,7 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe
EsError error;
if (StringEquals(PathGetParent(source), destinationBase)) {
if (move) {
if (task->move) {
// Move with the source and destination folders identical; meaningless.
error = ES_SUCCESS;
goto done;
@ -193,17 +246,19 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe
}
}
EsPrint("%z %s -> %s...\n", move ? "Moving" : "Copying", STRFMT(source), STRFMT(destination));
EsPrint("%z %s -> %s...\n", task->move ? "Moving" : "Copying", STRFMT(source), STRFMT(destination));
if (move) {
if (task->move) {
error = EsPathMove(STRING(source), STRING(destination), ES_FLAGS_DEFAULT);
if (error == ES_ERROR_VOLUME_MISMATCH) {
// TODO Delete the files after all copies complete successfully.
error = EsFileCopy(STRING(source), STRING(destination), copyBuffer);
goto copy;
}
} else {
error = EsFileCopy(STRING(source), STRING(destination), copyBuffer);
copy:;
task->lastBytesCopied = 0;
error = EsFileCopy(STRING(source), STRING(destination), copyBuffer, CommandPasteCopyCallback, task);
}
if (error == ES_ERROR_INCORRECT_NODE_TYPE) {
@ -219,7 +274,7 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe
for (intptr_t i = 0; i < childCount && error == ES_SUCCESS; i++) {
String childSourcePath = StringAllocateAndFormat("%s%z%s", STRFMT(source),
PathHasTrailingSlash(source) ? "" : "/", buffer[i].nameBytes, buffer[i].name);
error = CommandPasteFile(childSourcePath, destination, copyBuffer, move, nullptr);
error = CommandPasteFile(childSourcePath, destination, copyBuffer, task, nullptr);
StringDestroy(&childSourcePath);
}
}
@ -230,7 +285,7 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe
if (error == ES_SUCCESS) {
EsMessageMutexAcquire();
if (move) FolderFileUpdatedAtPath(source, nullptr);
if (task->move) FolderFileUpdatedAtPath(source, nullptr);
FolderFileUpdatedAtPath(destination, nullptr);
EsMessageMutexRelease();
}
@ -246,46 +301,75 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe
return error;
}
struct PasteOperation {
String source, destination;
};
struct PasteTask {
// Input:
String destinationBase;
bool move;
char *pathList;
size_t pathListBytes;
};
void CommandPasteTask(EsGeneric _task) {
// TODO Background task.
// TODO Reporting errors properly. Ask to retry or cancel.
// TODO If the destination file already exists, ask to replace, skip, rename or cancel.
void CommandPasteTask(EsUserTask *userTask, EsGeneric _task) {
// TODO Reporting errors properly. Ask to retry or skip.
// TODO If the destination file already exists, ask to rename or skip (as replace is destructive, it should be an advanced option).
// TODO Other namespace handlers.
// TODO Undo.
PasteTask *task = (PasteTask *) _task.p;
Array<PasteOperation> pasteOperations = {};
EsError error = ES_SUCCESS;
task->userTask = userTask;
void *copyBuffer = nullptr;
const char *position = task->pathList;
while (task->pathListBytes) {
const char *newline = (const char *) EsCRTmemchr(position, '\n', task->pathListBytes);
const char *position = task->pathList;
size_t remainingBytes = task->pathListBytes;
while (remainingBytes && EsUserTaskIsRunning(task->userTask)) {
const char *newline = (const char *) EsCRTmemchr(position, '\n', remainingBytes);
if (!newline) break;
String source = StringFromLiteralWithSize(position, newline - position);
if (!task->move || !StringEquals(PathGetDrive(source), PathGetDrive(task->destinationBase))) {
// Files are actually being copied, so report progress by the amount of data copied,
// rather than the amount of files processed.
task->progressByData = true;
}
EsDirectoryChild information;
if (EsPathQueryInformation(STRING(source), &information)) {
if (information.fileSize == -1) {
// TODO Support progress on volumes that don't report total directory sizes.
} else {
task->totalDataToProcess += information.fileSize;
}
task->sourceItemCount++;
} else {
// We will probably error on this file, so ignore it.
}
position += source.bytes + 1;
remainingBytes -= source.bytes + 1;
}
position = task->pathList;
remainingBytes = task->pathListBytes;
while (remainingBytes && EsUserTaskIsRunning(task->userTask)) {
const char *newline = (const char *) EsCRTmemchr(position, '\n', remainingBytes);
if (!newline) break;
String source = StringFromLiteralWithSize(position, newline - position);
String destination;
error = CommandPasteFile(source, task->destinationBase, &copyBuffer, task->move, &destination);
error = CommandPasteFile(source, task->destinationBase, &copyBuffer, task, &destination);
if (error != ES_SUCCESS) break;
PasteOperation operation = { .source = StringDuplicate(source), .destination = destination };
pasteOperations.Add(operation);
position += source.bytes + 1;
task->pathListBytes -= source.bytes + 1;
remainingBytes -= source.bytes + 1;
task->sourceItemsProcessed++;
if (!task->progressByData) {
EsUserTaskSetProgress(userTask, (double) task->sourceItemsProcessed / task->sourceItemCount, -1);
}
}
EsMessageMutexAcquire();
@ -357,7 +441,15 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) {
task->destinationBase = StringDuplicate(instance->folder->path);
instance->issuedPasteTask = task;
if (ES_SUCCESS != EsUserTaskStart(CommandPasteTask, task)) {
EsError error;
if (task->move) {
error = EsUserTaskStart(CommandPasteTask, task, INTERFACE_STRING(FileManagerMoveTask), ES_ICON_FOLDER_MOVE);
} else {
error = EsUserTaskStart(CommandPasteTask, task, INTERFACE_STRING(FileManagerCopyTask), ES_ICON_FOLDER_COPY);
}
if (error != ES_SUCCESS) {
EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list);
EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther));
EsHeapFree(task->pathList);

View File

@ -662,7 +662,7 @@ void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename)
thumbnail->generatingTasksInProgress++;
String path = StringAllocateAndFormat("%s%s", STRFMT(instance->path), STRFMT(entry->GetName()));
String path = StringAllocateAndFormat("%s%s", STRFMT(instance->path), STRFMT(entry->GetInternalName()));
Task task = {
.string = path,
@ -805,6 +805,11 @@ int ListCallback(EsElement *element, EsMessage *message) {
EsElement *element = message->createItem.item;
element->messageUser = ListItemMessage;
ListItemCreated(element, message->createItem.index, false);
} else if (message->type == ES_MSG_LIST_VIEW_CONTEXT_MENU) {
EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR);
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardCut), EsCommandByID(instance, ES_COMMAND_CUT));
EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(instance, ES_COMMAND_COPY));
EsMenuShow(menu);
} else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) {
EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR);

View File

@ -62,6 +62,7 @@ struct EnumString { const char *cName; int value; };
#define DESKTOP_MSG_FILE_TYPES_GET (14)
#define DESKTOP_MSG_UNHANDLED_KEY_EVENT (15)
#define DESKTOP_MSG_START_USER_TASK (16)
#define DESKTOP_MSG_SET_PROGRESS (17)
struct EsFileStore {
#define FILE_STORE_HANDLE (1)
@ -350,6 +351,23 @@ int64_t EsSystemConfigurationReadInteger(const char *section, ptrdiff_t sectionB
return EsSystemConfigurationGroupReadInteger(group, key, keyBytes, defaultValue);
}
void SystemConfigurationUnload() {
for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) {
for (uintptr_t j = 0; j < api.systemConfigurationGroups[i].itemCount; j++) {
EsHeapFree(api.systemConfigurationGroups[i].items[j].key);
EsHeapFree(api.systemConfigurationGroups[i].items[j].value);
}
EsHeapFree(api.systemConfigurationGroups[i].section);
EsHeapFree(api.systemConfigurationGroups[i].sectionClass);
Array<EsSystemConfigurationItem> items = { api.systemConfigurationGroups[i].items };
items.Free();
}
api.systemConfigurationGroups.Free();
}
void SystemConfigurationLoad(char *file, size_t fileBytes) {
EsINIState s = {};
s.buffer = file;
@ -784,6 +802,16 @@ EsMessage *EsMessageReceive() {
EsAssert(message.message.instanceSave.file->operationComplete);
FileStoreCloseHandle(message.message.instanceSave.file);
} else if (message.message.type == ES_MSG_APPLICATION_EXIT) {
#ifdef DEBUG_BUILD
GlyphCacheFree();
FreeUnusedStyles();
theming.loadedStyles.Free();
SystemConfigurationUnload();
api.mountPoints.Free();
api.postBox.Free();
api.timers.Free();
EsPrint("ES_MSG_APPLICATION_EXIT - Heap allocation count: %d.\n", heap.allocationsCount);
#endif
EsProcessTerminateCurrent();
} else if (message.message.type == ES_MSG_INSTANCE_DESTROY) {
api.openInstanceCount--;
@ -1181,7 +1209,7 @@ extern "C" void _start(EsProcessStartupInformation *_startupInformation) {
uint8_t m = DESKTOP_MSG_SYSTEM_CONFIGURATION_GET;
EsBuffer responseBuffer = { .canGrow = true };
MessageDesktop(&m, 1, ES_INVALID_HANDLE, &responseBuffer);
SystemConfigurationLoad((char *) responseBuffer.in, responseBuffer.bytes);
SystemConfigurationLoad((char *) responseBuffer.out, responseBuffer.bytes);
}
if (uiProcess) {
@ -1548,15 +1576,18 @@ const void *EsEmbeddedFileGet(const char *_name, ptrdiff_t nameBytes, size_t *by
return nullptr;
}
struct UserTask {
struct EsUserTask {
EsUserTaskCallback callback;
EsGeneric data;
EsHandle taskHandle;
#define USER_TASK_MINIMUM_TIME_BETWEEN_PROGRESS_MESSAGES_MS (50)
double lastProgressMs;
};
void UserTaskThread(EsGeneric _task) {
UserTask *task = (UserTask *) _task.p;
task->callback(task->data);
EsUserTask *task = (EsUserTask *) _task.p;
task->callback(task, task->data);
EsMessageMutexAcquire();
api.openInstanceCount--;
// TODO Send ES_MSG_APPLICATION_EXIT if needed.
@ -1566,10 +1597,32 @@ void UserTaskThread(EsGeneric _task) {
EsHeapFree(task);
}
EsError EsUserTaskStart(EsUserTaskCallback callback, EsGeneric data) {
void EsUserTaskSetProgress(EsUserTask *task, double progress, EsFileOffsetDifference bytesPerSecond) {
(void) bytesPerSecond;
double timeMs = EsTimeStampMs();
if (timeMs - task->lastProgressMs >= USER_TASK_MINIMUM_TIME_BETWEEN_PROGRESS_MESSAGES_MS) {
task->lastProgressMs = timeMs;
progress = ClampDouble(0.0, 1.0, progress);
uint8_t buffer[1 + sizeof(double)];
buffer[0] = DESKTOP_MSG_SET_PROGRESS;
EsMemoryCopy(buffer + 1, &progress, sizeof(double));
MessageDesktop(buffer, sizeof(buffer), task->taskHandle);
}
}
bool EsUserTaskIsRunning(EsUserTask *task) {
(void) task;
return true; // TODO.
}
EsError EsUserTaskStart(EsUserTaskCallback callback, EsGeneric data, const char *title, ptrdiff_t titleBytes, uint32_t iconID) {
// TODO Only tell the Desktop about the task if it's going to take >1 seconds.
// Maybe check after 500ms if the task is <50% complete?
EsMessageMutexCheck();
UserTask *task = (UserTask *) EsHeapAllocate(sizeof(UserTask), true);
EsUserTask *task = (EsUserTask *) EsHeapAllocate(sizeof(EsUserTask), true);
if (!task) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
@ -1587,6 +1640,15 @@ EsError EsUserTaskStart(EsUserTaskCallback callback, EsGeneric data) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
{
char buffer[4096];
buffer[0] = DESKTOP_MSG_SET_ICON;
EsMemoryCopy(buffer + 1, &iconID, sizeof(uint32_t));
MessageDesktop(buffer, 1 + sizeof(uint32_t), handle);
size_t bytes = EsStringFormat(buffer, 4096, "%c%s", DESKTOP_MSG_SET_TITLE, titleBytes == -1 ? EsCStringLength(title) : titleBytes, title);
MessageDesktop(buffer, bytes, handle);
}
task->callback = callback;
task->data = data;
task->taskHandle = handle;

View File

@ -151,6 +151,7 @@ struct ApplicationInstance {
char title[128];
size_t titleBytes;
uint32_t iconID;
double progress;
};
const EsStyle styleNewTabContent = {
@ -195,6 +196,9 @@ struct {
ClipboardInformation clipboardInformation;
bool configurationModified;
Array<ApplicationInstance *> allOngoingUserTasks;
double totalUserTaskProgress;
} desktop;
int TaskBarButtonMessage(EsElement *element, EsMessage *message);
@ -731,12 +735,53 @@ int TaskBarMessage(EsElement *element, EsMessage *message) {
return 0;
}
int TaskBarTasksButtonMessage(EsElement *, EsMessage *message) {
void TaskBarTasksButtonUpdate() {
if (desktop.allOngoingUserTasks.Length()) {
if (EsElementIsHidden(desktop.tasksButton)) {
EsPanelStartMovementAnimation((EsPanel *) EsElementGetLayoutParent(desktop.tasksButton), 1.5f /* duration scale */);
EsElementStartTransition(desktop.tasksButton, ES_TRANSITION_FADE_IN, ES_ELEMENT_TRANSITION_ENTRANCE, 1.5f);
EsElementSetHidden(desktop.tasksButton, false);
}
// TODO Maybe grow button.
} else {
// TODO Maybe shrink or hide button.
// Currently, this always hides it, but I don't think this is a good behaviour.
if (!EsElementIsHidden(desktop.tasksButton)) {
EsPanelStartMovementAnimation((EsPanel *) EsElementGetLayoutParent(desktop.tasksButton), 1.5f /* duration scale */);
EsElementStartTransition(desktop.tasksButton, ES_TRANSITION_FADE_OUT, ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE, 1.5f);
}
}
EsElementRepaint(desktop.tasksButton);
}
int TaskBarTasksButtonMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_GET_WIDTH) {
message->measure.width = GetConstantNumber("taskBarTasksButtonWidth");
return ES_HANDLED;
} else if (message->type == ES_MSG_PAINT) {
char title[256];
size_t titleBytes;
if (desktop.allOngoingUserTasks.Length() > 1) {
// TODO Localization.
titleBytes = EsStringFormat(title, sizeof(title), "%d tasks" ELLIPSIS, desktop.allOngoingUserTasks.Length());
} else if (desktop.allOngoingUserTasks.Length() == 1) {
titleBytes = EsStringFormat(title, sizeof(title), "%s", desktop.allOngoingUserTasks.First()->titleBytes, desktop.allOngoingUserTasks.First()->title);
} else {
titleBytes = 0;
}
EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), title, titleBytes);
} else if (message->type == ES_MSG_PAINT_ICON) {
float progress = 0.0f; // TODO.
double progress = !desktop.allOngoingUserTasks.Length() ? 1.0 : desktop.totalUserTaskProgress / desktop.allOngoingUserTasks.Length();
if (progress > 0.0 && progress <= 0.05) {
progress = 0.05; // Really small angles look strange; avoid them.
} else if (progress >= 0.9 && progress < 1.0) {
progress = (progress - 0.9) * 0.5 + 0.9; // Approach 95% from 90% at half speed to account for bad progress calculations.
}
uint32_t color1 = GetConstantNumber("taskBarTasksButtonWheelColor1");
uint32_t color2 = GetConstantNumber("taskBarTasksButtonWheelColor2");
@ -784,10 +829,11 @@ int TaskBarTasksButtonMessage(EsElement *, EsMessage *message) {
}
RastSurfaceDestroy(&surface);
return ES_HANDLED;
} else {
return 0;
}
return 0;
return ES_HANDLED;
}
void ShutdownModalCreate() {
@ -1888,7 +1934,7 @@ void DesktopSetup() {
desktop.taskBar.taskList.Initialise(panel, ES_CELL_FILL, ReorderListMessage, nullptr);
desktop.taskBar.taskList.cName = "task list";
desktop.tasksButton = EsButtonCreate(panel, ES_ELEMENT_HIDDEN, ES_STYLE_TASK_BAR_BUTTON, "Copying files" ELLIPSIS, -1);
desktop.tasksButton = EsButtonCreate(panel, ES_ELEMENT_HIDDEN, ES_STYLE_TASK_BAR_BUTTON);
desktop.tasksButton->messageUser = TaskBarTasksButtonMessage;
EsButton *shutdownButton = EsButtonCreate(panel, ES_FLAGS_DEFAULT, ES_STYLE_TASK_BAR_EXTRA);
@ -2095,15 +2141,13 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
EsHandle targetWindowHandle = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, windowHandle, processHandle, 0, ES_WINDOW_PROPERTY_EMBED_OWNER);
EsBufferWrite(pipe, &targetWindowHandle, sizeof(targetWindowHandle));
if (EsElementIsHidden(desktop.tasksButton)) {
EsPanelStartMovementAnimation((EsPanel *) EsElementGetLayoutParent(desktop.tasksButton), 1.5f /* duration scale */);
EsElementStartTransition(desktop.tasksButton, ES_TRANSITION_FADE_IN, ES_ELEMENT_TRANSITION_ENTRANCE, 1.5f);
EsElementSetHidden(desktop.tasksButton, false);
}
desktop.allOngoingUserTasks.Add(instance);
TaskBarTasksButtonUpdate();
} else if (!instance) {
// -------------------------------------------------
// | Messages below here require a valid instance. |
// -------------------------------------------------
EsPrint("DesktopSyscall - Received message %d without an instance.\n", buffer[0]);
} else if (buffer[0] == DESKTOP_MSG_SET_TITLE || buffer[0] == DESKTOP_MSG_SET_ICON) {
if (buffer[0] == DESKTOP_MSG_SET_TITLE) {
instance->titleBytes = EsStringFormat(instance->title, sizeof(instance->title), "%s",
@ -2121,6 +2165,15 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
instance->tab->container->taskBarButton->Repaint(true);
}
}
} else if (buffer[0] == DESKTOP_MSG_SET_PROGRESS && message->desktop.bytes == 1 + sizeof(double) && instance->isUserTask) {
double progress;
EsMemoryCopy(&progress, buffer + 1, sizeof(double));
if (progress >= 0.0 && progress <= 1.0) {
desktop.totalUserTaskProgress += progress - instance->progress;
instance->progress = progress;
EsElementRepaint(desktop.tasksButton);
}
} else if (buffer[0] == DESKTOP_MSG_REQUEST_SAVE) {
ApplicationInstanceRequestSave(instance, (const char *) buffer + 1, message->desktop.bytes - 1);
} else if (buffer[0] == DESKTOP_MSG_COMPLETE_SAVE) {
@ -2163,6 +2216,8 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
if (message.message.type != ES_MSG_INVALID) {
UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr);
}
} else {
EsPrint("DesktopSyscall - Received unhandled message %d.\n", buffer[0]);
}
}
@ -2218,6 +2273,11 @@ void EmbeddedWindowDestroyed(EsObjectID id) {
EsElementDestroy(instance->tab);
}
} else if (instance->isUserTask) {
desktop.totalUserTaskProgress -= instance->progress;
EsElementRepaint(desktop.tasksButton);
desktop.allOngoingUserTasks.FindAndDeleteSwap(instance, false /* ignore if not found */);
TaskBarTasksButtonUpdate();
}
EsHeapFree(instance);

View File

@ -125,7 +125,7 @@ struct EsElement : EsElementPublic {
Array<EsElement *> children;
uint32_t state;
uint8_t transitionType;
uint8_t transitionType, transitionFlags;
uint16_t customStyleState; // ORed to the style state in RefreshStyle.
uint16_t previousStyleState; // Set by RefreshStyleState.
uint16_t transitionDurationMs, transitionTimeMs;
@ -337,6 +337,7 @@ struct EsPanel : EsElement {
EsPanelBand *bands[2];
uintptr_t tableIndex;
// TODO This names overlap with fields in EsElement, they should probably be renamed.
uint16_t transitionType;
uint32_t transitionTimeMs,
transitionLengthMs;
@ -1308,6 +1309,10 @@ EsRectangle UIGetTransitionEffectRectangle(EsRectangle bounds, EsTransitionType
}
void UIDrawTransitionEffect(EsPainter *painter, EsPaintTarget *sourceSurface, EsRectangle bounds, EsTransitionType type, double progress, bool to) {
if (type == ES_TRANSITION_FADE_OUT && to) {
return;
}
EsRectangle destinationRegion = UIGetTransitionEffectRectangle(bounds, type, progress, to);
EsRectangle sourceRegion = ES_RECT_4(0, bounds.r - bounds.l, 0, bounds.b - bounds.t);
uint16_t alpha = (to ? progress : (1 - progress)) * 255;
@ -1344,6 +1349,7 @@ void EsElementStartTransition(EsElement *element, EsTransitionType transitionTyp
}
element->transitionTimeMs = 0;
element->transitionFlags = flags;
element->transitionDurationMs = durationMs;
element->transitionType = transitionType;
element->StartAnimating();
@ -1673,6 +1679,10 @@ void ProcessAnimations() {
if (!transitionComplete) {
element->Repaint(true, ES_RECT_1(0));
} else {
if (element->transitionFlags & ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE) {
EsElementSetHidden(element, true);
}
}
bool backgroundAnimationComplete = ThemeAnimationComplete(&element->animation);
@ -6248,7 +6258,10 @@ void UIHandleKeyMessage(EsWindow *window, EsMessage *message) {
if (window->focused) {
message->type = ES_MSG_KEY_TYPED;
if (EsMessageSend(window->focused, message) != 0) return;
if (EsMessageSend(window->focused, message) == ES_HANDLED /* allow messageUser to reject input */) {
return;
}
EsElement *element = window->focused;
message->type = ES_MSG_KEY_DOWN;

View File

@ -19,6 +19,7 @@ opaque_type EsPaintTarget none;
opaque_type EsUndoManager none;
opaque_type EsHeap none;
opaque_type EsFileStore none;
opaque_type EsUserTask none;
type_name uint8_t EsNodeType;
type_name intptr_t EsError;
@ -326,6 +327,7 @@ define ES_ERROR_CONNECTION_REFUSED (-68)
define ES_ERROR_ILLEGAL_PATH (-69)
define ES_ERROR_NODE_NOT_LOADED (-71)
define ES_ERROR_DIRECTORY_ENTRY_BEING_REMOVED (-72)
define ES_ERROR_CANCELLED (-73)
define ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND (0)
define ES_SYSTEM_CONSTANT_OPTIMAL_WORK_QUEUE_THREAD_COUNT (1)
@ -666,6 +668,7 @@ define ES_MEMORY_RESERVE_COMMIT_ALL (1 << 0)
define ES_PANEL_SWITCHER_DESTROY_PREVIOUS_AFTER_TRANSITION (1 << 0)
define ES_ELEMENT_TRANSITION_ENTRANCE (1 << 0)
define ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE (1 << 1)
define ES_TEXT_GET_CHARACTER_AT_POINT_MIDDLE (1 << 0)
@ -910,7 +913,7 @@ enum EsMessageType {
// It will be removed from the `children` later (but before the next ES_MSG_LAYOUT message is received).
ES_MSG_PRE_ADD_CHILD = 0x200D // An element has been created with this element as its parent, but is not yet added to the parent.
ES_MSG_HIT_TEST = 0x200E // For non-rectangular elements: test whether a pixel should be considered inside the element. Set response to ES_HANDLED.
ES_MSG_KEY_TYPED = 0x2011 // Sent to the focused element when a key is pressed.
ES_MSG_KEY_TYPED = 0x2011 // Sent to the focused element when a key is pressed. Only if ES_HANDLED is returned the message will not propagate; this allows messageUser to block input processing by returning ES_REJECTED.
ES_MSG_SCROLL_X = 0x2012 // The element has been horizontally scrolled.
ES_MSG_SCROLL_Y = 0x2013 // The element has been vertically scrolled.
ES_MSG_STRONG_FOCUS_END = 0x2014 // Sent once when the user 'clicks off' the element, even if a new element was not necessarily focused.
@ -1887,7 +1890,8 @@ function_pointer void EsUndoCallback(const void *item, EsUndoManager *manager, E
function_pointer void EsMountPointEnumerationCallback(const char *prefix, size_t prefixBytes, EsGeneric context);
function_pointer void EsListViewEnumerateVisibleItemsCallback(EsListView *view, EsElement *item, uint32_t group, EsGeneric index);
function_pointer void EsFontEnumerationCallback(const EsFontInformation *information, EsGeneric context);
function_pointer void EsUserTaskCallback(EsGeneric data);
function_pointer void EsUserTaskCallback(EsUserTask *task, EsGeneric data);
function_pointer bool EsFileCopyCallback(EsFileOffset bytesCopied, EsFileOffset totalBytes, EsGeneric data); // Return false to cancel.
// System.
@ -1933,7 +1937,7 @@ function EsError EsFileWriteAllFromHandle(EsHandle handle, const void *data, siz
function EsError EsFileWriteAllGather(STRING filePath, const void **data, size_t *fileSize, size_t gatherCount);
function EsError EsFileWriteAllGatherFromHandle(EsHandle handle, const void **data, size_t *fileSize, size_t gatherCount);
function void *EsFileMap(STRING filePath, size_t *fileSize, uint32_t flags);
function EsError EsFileCopy(STRING source, STRING destination, void **copyBuffer = nullptr); // If you are copying lots of files, you can reuse the temporary copy buffer by storing the output copyBuffer; call EsHeapFree on it after the last copy.
function EsError EsFileCopy(STRING source, STRING destination, void **copyBuffer = ES_NULL, EsFileCopyCallback callback = ES_NULL, EsGeneric data = ES_NULL); // If you are copying lots of files, you can reuse the temporary copy buffer by storing the output copyBuffer; call EsHeapFree on it after the last copy.
function EsError EsFileControl(EsHandle file, uint32_t flags);
function EsFileInformation EsFileOpen(STRING path, uint32_t flags);
@ -2233,7 +2237,7 @@ function int EsCRTvsnprintf(char *buffer, size_t bufferSize, const char *format,
function EsError EsClipboardAddText(EsClipboard clipboard, STRING text = BLANK_STRING);
function bool EsClipboardHasFormat(EsClipboard clipboard, EsClipboardFormat format);
function bool EsClipboardHasData(EsClipboard clipboard);
function char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes, uint32_t *flags = nullptr); // Free with EsHeapFree.
function char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes, uint32_t *flags = ES_NULL); // Free with EsHeapFree.
function EsFileStore *EsClipboardOpen(EsClipboard clipboard); // Open the clipboard for writing.
function EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, EsFileStore *fileStore, uint32_t flags = ES_FLAGS_DEFAULT);
@ -2265,7 +2269,9 @@ function const EsApplicationStartupInformation *EsInstanceGetStartupInformation(
function void EsInstanceOpenComplete(EsMessage *message, bool success, STRING errorText = BLANK_STRING);
function void EsInstanceSaveComplete(EsMessage *message, bool success);
function EsError EsUserTaskStart(EsUserTaskCallback callback, EsGeneric data);
function EsError EsUserTaskStart(EsUserTaskCallback callback, EsGeneric data, STRING title, uint32_t iconID);
function void EsUserTaskSetProgress(EsUserTask *task, double progress, EsFileOffsetDifference bytesPerSecond); // Set bytesPerSecond to -1 if not applicable.
function bool EsUserTaskIsRunning(EsUserTask *task); // Returns false if the task was cancelled.
// Message processing.

View File

@ -223,7 +223,8 @@ void *EsFileReadAll(const char *filePath, ptrdiff_t filePathLength, size_t *file
return buffer;
}
EsError EsFileCopy(const char *source, ptrdiff_t sourceBytes, const char *destination, ptrdiff_t destinationBytes, void **_copyBuffer) {
EsError EsFileCopy(const char *source, ptrdiff_t sourceBytes, const char *destination, ptrdiff_t destinationBytes, void **_copyBuffer,
EsFileCopyCallback callback, EsGeneric callbackData) {
const size_t copyBufferBytes = 262144;
void *copyBuffer = _copyBuffer && *_copyBuffer ? *_copyBuffer : EsHeapAllocate(copyBufferBytes, false);
if (_copyBuffer) *_copyBuffer = copyBuffer;
@ -245,9 +246,25 @@ EsError EsFileCopy(const char *source, ptrdiff_t sourceBytes, const char *destin
if (error == ES_SUCCESS) {
for (uintptr_t i = 0; i < sourceFile.size; i += copyBufferBytes) {
size_t bytesRead = EsFileReadSync(sourceFile.handle, i, copyBufferBytes, copyBuffer);
if (ES_CHECK_ERROR(bytesRead)) { error = bytesRead; break; }
if (ES_CHECK_ERROR(bytesRead)) {
error = bytesRead;
break;
}
size_t bytesWritten = EsFileWriteSync(destinationFile.handle, i, bytesRead, copyBuffer);
if (ES_CHECK_ERROR(bytesWritten)) { error = bytesWritten; break; }
if (ES_CHECK_ERROR(bytesWritten)) {
error = bytesWritten;
break;
}
EsAssert(bytesRead == bytesWritten);
if (callback && !callback(i + bytesWritten, sourceFile.size, callbackData)) {
error = ES_ERROR_CANCELLED;
break;
}
}
}

View File

@ -95,22 +95,25 @@ Font FontGet(EsFont key);
// --------------------------------- Glyph cache.
void GlyphCacheFreeEntry() {
GlyphCacheEntry *entry = fontManagement.glyphCacheLRU.lastItem->thisItem;
fontManagement.glyphCacheLRU.Remove(&entry->itemLRU);
fontManagement.glyphCache.Delete(&entry->key);
EsAssert(fontManagement.glyphCacheBytes >= entry->dataBytes);
fontManagement.glyphCacheBytes -= entry->dataBytes;
EsHeapFree(entry->data);
EsHeapFree(entry);
}
void RegisterGlyphCacheEntry(GlyphCacheKey key, GlyphCacheEntry *entry) {
entry->itemLRU.thisItem = entry;
entry->key = key;
*fontManagement.glyphCache.Put(&key) = entry;
fontManagement.glyphCacheLRU.InsertStart(&entry->itemLRU);
fontManagement.glyphCacheBytes += entry->dataBytes;
while (fontManagement.glyphCacheBytes > GLYPH_CACHE_MAX_SIZE) {
GlyphCacheEntry *leastRecentlyUsedGlyph = fontManagement.glyphCacheLRU.lastItem->thisItem;
fontManagement.glyphCacheLRU.Remove(&leastRecentlyUsedGlyph->itemLRU);
fontManagement.glyphCache.Delete(&leastRecentlyUsedGlyph->key);
EsAssert(fontManagement.glyphCacheBytes >= entry->dataBytes); // Negative glyph cache bytes.
fontManagement.glyphCacheBytes -= entry->dataBytes;
EsHeapFree(leastRecentlyUsedGlyph->data);
EsHeapFree(leastRecentlyUsedGlyph);
GlyphCacheFreeEntry();
}
}
@ -126,6 +129,15 @@ GlyphCacheEntry *LookupGlyphCacheEntry(GlyphCacheKey key) {
}
}
void GlyphCacheFree() {
while (fontManagement.glyphCacheLRU.count) {
GlyphCacheFreeEntry();
}
EsAssert(fontManagement.glyphCache.Count() == 0);
fontManagement.glyphCache.Free();
}
// --------------------------------- Font renderer.
bool FontLoad(Font *font, const void *data, size_t dataBytes) {

View File

@ -260,6 +260,8 @@ DEFINE_INTERFACE_STRING(FileManagerInvalidPath, "The current path does not lead
DEFINE_INTERFACE_STRING(FileManagerInvalidDrive, "The drive containing this folder was disconnected.");
DEFINE_INTERFACE_STRING(FileManagerRefresh, "Refresh");
DEFINE_INTERFACE_STRING(FileManagerListContextActions, "Actions");
DEFINE_INTERFACE_STRING(FileManagerCopyTask, "Copying" ELLIPSIS);
DEFINE_INTERFACE_STRING(FileManagerMoveTask, "Moving" ELLIPSIS);
// TODO System Monitor.

View File

@ -434,3 +434,5 @@ EsListViewSelectNone=432
EsElementIsFocused=433
EsUserTaskStart=434
EsElementIsHidden=435
EsUserTaskSetProgress=436
EsUserTaskIsRunning=437