mirror of https://gitlab.com/nakst/essence
improve tasks button
This commit is contained in:
parent
ecdce9e720
commit
6bdb99a79b
|
@ -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, ©Buffer, task->move, &destination);
|
||||
error = CommandPasteFile(source, task->destinationBase, ©Buffer, 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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -434,3 +434,5 @@ EsListViewSelectNone=432
|
|||
EsElementIsFocused=433
|
||||
EsUserTaskStart=434
|
||||
EsElementIsHidden=435
|
||||
EsUserTaskSetProgress=436
|
||||
EsUserTaskIsRunning=437
|
||||
|
|
Loading…
Reference in New Issue