remove old text code; organize UI code

This commit is contained in:
nakst 2021-12-25 20:31:31 +00:00
parent 69f46115b6
commit 1f92f55e46
11 changed files with 3156 additions and 3476 deletions

View File

@ -241,19 +241,13 @@ struct APIInstance {
EsTextbox *fileMenuNameTextbox; // Also used by the file save dialog.
};
#define CHARACTER_MONO (1) // 1 bit per pixel.
#define CHARACTER_SUBPIXEL (2) // 24 bits per pixel; each byte specifies the alpha of each RGB channel.
#define CHARACTER_IMAGE (3) // 32 bits per pixel, ARGB.
#define CHARACTER_RECOLOR (4) // 32 bits per pixel, AXXX.
#include "syscall.cpp"
#include "profiling.cpp"
#include "renderer.cpp"
#include "theme.cpp"
#define TEXT_RENDERER
#include "text.cpp"
#undef TEXT_RENDERER
#include "profiling.cpp"
#include "gui.cpp"
#include "inspector.cpp"
#ifndef NO_API_TABLE
const void *const apiTable[] = {

View File

@ -460,8 +460,6 @@ void HeapDuplicate(void **pointer, size_t *outBytes, const void *data, size_t by
}
}
// --------------------------------- Windows.
struct EsWindow : EsElement {
EsHandle handle;
EsObjectID id;
@ -3043,6 +3041,14 @@ EsScrollView *EsCustomScrollViewCreate(EsElement *parent, uint64_t flags, const
return element;
}
// --------------------------------- Textboxes.
#include "textbox.cpp"
// --------------------------------- List views.
#include "list_view.cpp"
// --------------------------------- Panels.
void PanelSwitcherTransitionComplete(EsPanel *panel) {
@ -3889,11 +3895,275 @@ EsCanvasPane *EsCanvasPaneCreate(EsElement *parent, uint64_t flags, const EsStyl
return pane;
}
// --------------------------------- Text displays and textboxes.
// --------------------------------- Text displays.
#define TEXT_ELEMENTS
#include "text.cpp"
#undef TEXT_ELEMENTS
// TODO Inline images and icons.
// TODO Links.
// TODO Inline backgrounds.
void TextDisplayFreeRuns(EsTextDisplay *display) {
if (display->usingSyntaxHighlighting) {
Array<EsTextRun> textRuns = { display->textRuns };
textRuns.Free();
} else {
EsHeapFree(display->textRuns);
}
}
int ProcessTextDisplayMessage(EsElement *element, EsMessage *message) {
EsTextDisplay *display = (EsTextDisplay *) element;
if (message->type == ES_MSG_PAINT) {
EsRectangle textBounds = EsPainterBoundsInset(message->painter);
if (!display->plan || display->planWidth != textBounds.r - textBounds.l || display->planHeight != textBounds.b - textBounds.t) {
if (display->plan) EsTextPlanDestroy(display->plan);
display->properties.flags = display->style->textAlign;
if (~display->flags & ES_TEXT_DISPLAY_PREFORMATTED) display->properties.flags |= ES_TEXT_PLAN_TRIM_SPACES;
if (display->flags & ES_TEXT_DISPLAY_NO_FONT_SUBSTITUTION) display->properties.flags |= ES_TEXT_PLAN_NO_FONT_SUBSTITUTION;
display->plan = EsTextPlanCreate(element, &display->properties, textBounds, display->contents, display->textRuns, display->textRunCount);
display->planWidth = textBounds.r - textBounds.l;
display->planHeight = textBounds.b - textBounds.t;
}
if (display->plan) {
EsDrawTextLayers(message->painter, display->plan, EsPainterBoundsInset(message->painter));
}
} else if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) {
if (!display->measurementCache.Get(message, &display->state)) {
if (display->plan) EsTextPlanDestroy(display->plan);
display->properties.flags = display->style->textAlign | ((display->flags & ES_TEXT_DISPLAY_PREFORMATTED) ? 0 : ES_TEXT_PLAN_TRIM_SPACES);
EsRectangle insets = EsElementGetInsets(element);
display->planWidth = message->type == ES_MSG_GET_HEIGHT && message->measure.width
? (message->measure.width - insets.l - insets.r) : 0;
display->planHeight = 0;
display->plan = EsTextPlanCreate(element, &display->properties,
ES_RECT_4(0, display->planWidth, 0, 0),
display->contents, display->textRuns, display->textRunCount);
if (!display->plan) {
message->measure.width = message->measure.height = 0;
} else {
if (message->type == ES_MSG_GET_WIDTH) {
message->measure.width = EsTextPlanGetWidth(display->plan) + insets.l + insets.r;
} else {
message->measure.height = EsTextPlanGetHeight(display->plan) + insets.t + insets.b;
}
}
display->measurementCache.Store(message);
}
} else if (message->type == ES_MSG_DESTROY) {
if (display->plan) {
EsTextPlanDestroy(display->plan);
}
TextDisplayFreeRuns(display);
EsHeapFree(display->contents);
} else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) {
EsBufferFormat(message->getContent.buffer, "'%s'", display->textRuns[display->textRunCount].offset, display->contents);
} else if (message->type == ES_MSG_UI_SCALE_CHANGED) {
if (display->plan) {
EsTextPlanDestroy(display->plan);
display->plan = nullptr;
}
} else {
return 0;
}
return ES_HANDLED;
}
void EsTextDisplaySetStyledContents(EsTextDisplay *display, const char *string, EsTextRun *runs, size_t runCount) {
TextDisplayFreeRuns(display);
display->textRuns = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * (runCount + 1), true);
display->textRunCount = runCount;
size_t outBytes;
HeapDuplicate((void **) &display->contents, &outBytes, string, runs[runCount].offset);
if (outBytes != runs[runCount].offset) {
// TODO Handle allocation failure.
}
EsMemoryCopy(display->textRuns, runs, sizeof(EsTextRun) * (runCount + 1));
display->usingSyntaxHighlighting = false;
EsElementUpdateContentSize(display);
InspectorNotifyElementContentChanged(display);
}
void EsTextDisplaySetContents(EsTextDisplay *display, const char *string, ptrdiff_t stringBytes) {
if (stringBytes == -1) stringBytes = EsCStringLength(string);
TextDisplayFreeRuns(display);
if (display->flags & ES_TEXT_DISPLAY_RICH_TEXT) {
EsHeapFree(display->contents);
EsTextStyle baseStyle = {};
display->style->GetTextStyle(&baseStyle);
EsRichTextParse(string, stringBytes, &display->contents, &display->textRuns, &display->textRunCount, &baseStyle);
} else {
HeapDuplicate((void **) &display->contents, (size_t *) &stringBytes, string, stringBytes);
display->textRuns = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * 2, true);
display->style->GetTextStyle(&display->textRuns[0].style);
display->textRuns[1].offset = stringBytes;
display->textRunCount = 1;
}
display->usingSyntaxHighlighting = false;
EsElementUpdateContentSize(display);
InspectorNotifyElementContentChanged(display);
}
EsTextDisplay *EsTextDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style, const char *label, ptrdiff_t labelBytes) {
EsTextDisplay *display = (EsTextDisplay *) EsHeapAllocate(sizeof(EsTextDisplay), true);
if (!display) return nullptr;
display->Initialise(parent, flags, ProcessTextDisplayMessage, style ?: UIGetDefaultStyleVariant(ES_STYLE_TEXT_LABEL, parent));
display->cName = "text display";
if (labelBytes == -1) labelBytes = EsCStringLength(label);
EsTextDisplaySetContents(display, label, labelBytes);
return display;
}
void EsTextDisplaySetupSyntaxHighlighting(EsTextDisplay *display, uint32_t language, uint32_t *customColors, size_t customColorCount) {
// Copied from EsTextboxSetupSyntaxHighlighting.
uint32_t colors[8];
colors[0] = 0x04000000; // Highlighted line.
colors[1] = 0xFF000000; // Default.
colors[2] = 0xFFA11F20; // Comment.
colors[3] = 0xFF037E01; // String.
colors[4] = 0xFF213EF1; // Number.
colors[5] = 0xFF7F0480; // Operator.
colors[6] = 0xFF545D70; // Preprocessor.
colors[7] = 0xFF17546D; // Keyword.
if (customColorCount > sizeof(colors) / sizeof(uint32_t)) customColorCount = sizeof(colors) / sizeof(uint32_t);
EsMemoryCopy(colors, customColors, customColorCount * sizeof(uint32_t));
EsTextStyle textStyle = {};
display->style->GetTextStyle(&textStyle);
EsTextRun *newRuns = TextApplySyntaxHighlighting(&textStyle, language, colors, {},
display->contents, display->textRuns[display->textRunCount].offset).array;
TextDisplayFreeRuns(display);
display->textRuns = newRuns;
display->textRunCount = ArrayLength(display->textRuns) - 1;
display->usingSyntaxHighlighting = true;
display->Repaint(true);
}
// --------------------------------- List displays.
struct EsListDisplay : EsElement {
uintptr_t itemCount, startIndex;
EsListDisplay *previous;
};
int ProcessListDisplayMessage(EsElement *element, EsMessage *message) {
EsListDisplay *display = (EsListDisplay *) element;
if (message->type == ES_MSG_GET_HEIGHT) {
int32_t height = 0;
int32_t margin = element->style->insets.l + element->style->insets.r + element->style->gapMinor;
uintptr_t itemCount = 0;
for (uintptr_t i = 0; i < element->GetChildCount(); i++) {
EsElement *child = element->GetChild(i);
if (child->flags & ES_ELEMENT_NON_CLIENT) continue;
height += child->GetHeight(message->measure.width - margin);
itemCount++;
}
if (itemCount) {
height += (itemCount - 1) * element->style->gapMajor;
}
message->measure.height = height + element->style->insets.t + element->style->insets.b;
} else if (message->type == ES_MSG_LAYOUT) {
int32_t position = element->style->insets.t;
int32_t margin = element->style->insets.l + element->style->gapMinor;
int32_t width = element->width - margin - element->style->insets.r;
for (uintptr_t i = 0; i < element->GetChildCount(); i++) {
EsElement *child = element->GetChild(i);
if (child->flags & ES_ELEMENT_NON_CLIENT) continue;
int height = child->GetHeight(width);
EsElementMove(child, margin, position, width, height);
position += height + element->style->gapMajor;
}
} else if (message->type == ES_MSG_PAINT) {
char buffer[64];
EsTextPlanProperties properties = {};
properties.flags = ES_TEXT_H_RIGHT | ES_TEXT_V_TOP | ES_TEXT_PLAN_SINGLE_USE;
EsTextRun textRun[2] = {};
EsRectangle bounds = EsPainterBoundsClient(message->painter);
bounds.r = bounds.l + element->style->insets.l;
uintptr_t counter = display->previous ? display->previous->itemCount : display->startIndex;
uint8_t markerType = element->flags & ES_LIST_DISPLAY_MARKER_TYPE_MASK;
EsMessage m = {};
m.type = ES_MSG_LIST_DISPLAY_GET_MARKER;
EsBuffer buffer2 = { .out = (uint8_t *) buffer, .bytes = sizeof(buffer) };
m.getContent.buffer = &buffer2;
for (uintptr_t i = 0; i < element->GetChildCount(); i++) {
EsElement *child = element->GetChild(i);
if (child->flags & ES_ELEMENT_NON_CLIENT) continue;
if (markerType == ES_LIST_DISPLAY_BULLETED) {
EsMemoryCopy(buffer, "\xE2\x80\xA2", (textRun[1].offset = 3));
} else if (markerType == ES_LIST_DISPLAY_NUMBERED) {
textRun[1].offset = EsStringFormat(buffer, sizeof(buffer), "%d.", counter + 1);
} else if (markerType == ES_LIST_DISPLAY_LOWER_ALPHA) {
textRun[1].offset = EsStringFormat(buffer, sizeof(buffer), "(%c)", counter + 'a');
} else if (markerType == ES_LIST_DISPLAY_CUSTOM_MARKER) {
m.getContent.index = counter;
EsMessageSend(element, &m);
textRun[1].offset = buffer2.position;
} else {
EsAssert(false);
}
child->style->GetTextStyle(&textRun[0].style);
textRun[0].style.figures = ES_TEXT_FIGURE_TABULAR;
bounds.t += child->offsetY;
bounds.b = bounds.t + child->height;
EsTextPlan *plan = EsTextPlanCreate(element, &properties, bounds, buffer, textRun, 1);
if (plan) EsDrawText(message->painter, plan, bounds);
bounds.t -= child->offsetY;
counter++;
}
} else if (message->type == ES_MSG_ADD_CHILD) {
display->itemCount++;
} else if (message->type == ES_MSG_REMOVE_CHILD) {
display->itemCount--;
}
return 0;
}
EsListDisplay *EsListDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style) {
EsListDisplay *display = (EsListDisplay *) EsHeapAllocate(sizeof(EsListDisplay), true);
if (!display) return nullptr;
display->Initialise(parent, flags, ProcessListDisplayMessage, style ?: ES_STYLE_LIST_DISPLAY_DEFAULT);
display->cName = "list display";
return display;
}
void EsListDisplaySetCounterContinuation(EsListDisplay *display, EsListDisplay *previous) {
display->previous = previous;
EsElementRepaint(display);
}
void EsListDisplaySetCounterStart(EsListDisplay *display, uintptr_t index) {
display->startIndex = index;
display->previous = nullptr;
EsElementRepaint(display);
}
// --------------------------------- Announcements.
@ -7596,612 +7866,3 @@ void UIProcessWindowManagerMessage(EsWindow *window, EsMessage *message, Process
window->willUpdate = false;
}
// --------------------------------- List view.
#include "list_view.cpp"
// --------------------------------- Inspector.
struct InspectorElementEntry {
EsElement *element;
EsRectangle takenBounds, givenBounds;
int depth;
};
struct InspectorWindow : EsInstance {
EsInstance *instance; // The instance being inspected.
EsListView *elementList;
Array<InspectorElementEntry> elements; // TODO This is being leaked.
InspectorElementEntry hoveredElement;
char *cCategoryFilter;
intptr_t selectedElement;
EsButton *alignH[6];
EsButton *alignV[6];
EsButton *direction[4];
EsTextbox *contentTextbox;
EsButton *addChildButton;
EsButton *addSiblingButton;
EsButton *visualizeRepaints;
EsButton *visualizeLayoutBounds;
EsButton *visualizePaintSteps;
EsListView *listEvents;
EsTextbox *textboxCategoryFilter;
};
int InspectorElementItemCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_HOVERED_START) {
InspectorElementEntry *entry = &inspector->elements[EsListViewGetIndexFromItem(element)];
if (entry->element->parent) entry->element->parent->Repaint(true);
else entry->element->Repaint(true);
inspector->hoveredElement = *entry;
} else if (message->type == ES_MSG_HOVERED_END || message->type == ES_MSG_DESTROY) {
EsListViewIndex index = EsListViewGetIndexFromItem(element);
InspectorElementEntry *entry = &inspector->elements[index];
if (entry->element->parent) entry->element->parent->Repaint(true);
else entry->element->Repaint(true);
inspector->hoveredElement = {};
}
return 0;
}
void InspectorUpdateEditor(InspectorWindow *inspector) {
EsElement *e = inspector->selectedElement == -1 ? nullptr : inspector->elements[inspector->selectedElement].element;
bool isStack = e && e->messageClass == ProcessPanelMessage && !(e->flags & (ES_PANEL_Z_STACK | ES_PANEL_TABLE | ES_PANEL_SWITCHER));
bool alignHLeft = e ? (e->flags & ES_CELL_H_LEFT) : false, alignHRight = e ? (e->flags & ES_CELL_H_RIGHT) : false;
bool alignHExpand = e ? (e->flags & ES_CELL_H_EXPAND) : false, alignHShrink = e ? (e->flags & ES_CELL_H_SHRINK) : false;
bool alignHPush = e ? (e->flags & ES_CELL_H_PUSH) : false;
bool alignVTop = e ? (e->flags & ES_CELL_V_TOP) : false, alignVBottom = e ? (e->flags & ES_CELL_V_BOTTOM) : false;
bool alignVExpand = e ? (e->flags & ES_CELL_V_EXPAND) : false, alignVShrink = e ? (e->flags & ES_CELL_V_SHRINK) : false;
bool alignVPush = e ? (e->flags & ES_CELL_V_PUSH) : false;
bool stackHorizontal = isStack && (e->flags & ES_PANEL_HORIZONTAL);
bool stackReverse = isStack && (e->flags & ES_PANEL_REVERSE);
EsButtonSetCheck(inspector->alignH[0], (EsCheckState) (e && alignHLeft && !alignHRight), false);
EsButtonSetCheck(inspector->alignH[1], (EsCheckState) (e && alignHLeft == alignHRight), false);
EsButtonSetCheck(inspector->alignH[2], (EsCheckState) (e && !alignHLeft && alignHRight), false);
EsButtonSetCheck(inspector->alignH[3], (EsCheckState) (e && alignHExpand), false);
EsButtonSetCheck(inspector->alignH[4], (EsCheckState) (e && alignHShrink), false);
EsButtonSetCheck(inspector->alignH[5], (EsCheckState) (e && alignHPush), false);
EsButtonSetCheck(inspector->alignV[0], (EsCheckState) (e && alignVTop && !alignVBottom), false);
EsButtonSetCheck(inspector->alignV[1], (EsCheckState) (e && alignVTop == alignVBottom), false);
EsButtonSetCheck(inspector->alignV[2], (EsCheckState) (e && !alignVTop && alignVBottom), false);
EsButtonSetCheck(inspector->alignV[3], (EsCheckState) (e && alignVExpand), false);
EsButtonSetCheck(inspector->alignV[4], (EsCheckState) (e && alignVShrink), false);
EsButtonSetCheck(inspector->alignV[5], (EsCheckState) (e && alignVPush), false);
EsButtonSetCheck(inspector->direction[0], (EsCheckState) (isStack && stackHorizontal && stackReverse), false);
EsButtonSetCheck(inspector->direction[1], (EsCheckState) (isStack && stackHorizontal && !stackReverse), false);
EsButtonSetCheck(inspector->direction[2], (EsCheckState) (isStack && !stackHorizontal && stackReverse), false);
EsButtonSetCheck(inspector->direction[3], (EsCheckState) (isStack && !stackHorizontal && !stackReverse), false);
EsElementSetDisabled(inspector->alignH[0], !e);
EsElementSetDisabled(inspector->alignH[1], !e);
EsElementSetDisabled(inspector->alignH[2], !e);
EsElementSetDisabled(inspector->alignH[3], !e);
EsElementSetDisabled(inspector->alignH[4], !e);
EsElementSetDisabled(inspector->alignH[5], !e);
EsElementSetDisabled(inspector->alignV[0], !e);
EsElementSetDisabled(inspector->alignV[1], !e);
EsElementSetDisabled(inspector->alignV[2], !e);
EsElementSetDisabled(inspector->alignV[3], !e);
EsElementSetDisabled(inspector->alignV[4], !e);
EsElementSetDisabled(inspector->alignV[5], !e);
EsElementSetDisabled(inspector->direction[0], !isStack);
EsElementSetDisabled(inspector->direction[1], !isStack);
EsElementSetDisabled(inspector->direction[2], !isStack);
EsElementSetDisabled(inspector->direction[3], !isStack);
EsElementSetDisabled(inspector->addChildButton, !isStack);
EsElementSetDisabled(inspector->addSiblingButton, !e || !e->parent);
EsElementSetDisabled(inspector->textboxCategoryFilter, !e);
EsTextboxSelectAll(inspector->contentTextbox);
EsTextboxInsert(inspector->contentTextbox, "", 0, false);
if (e) {
#if 0
for (uintptr_t i = 0; i < sizeof(builtinStyles) / sizeof(builtinStyles[0]); i++) {
if (e->currentStyleKey.partHash == CalculateCRC64(EsLiteral(builtinStyles[i]))) {
EsTextboxInsert(inspector->styleTextbox, builtinStyles[i], -1, false);
break;
}
}
#endif
if (e->messageClass == ProcessButtonMessage) {
EsButton *button = (EsButton *) e;
EsElementSetDisabled(inspector->contentTextbox, false);
EsTextboxInsert(inspector->contentTextbox, button->label, button->labelBytes, false);
} else if (e->messageClass == ProcessTextDisplayMessage) {
EsTextDisplay *display = (EsTextDisplay *) e;
EsElementSetDisabled(inspector->contentTextbox, false);
EsTextboxInsert(inspector->contentTextbox, display->contents, display->textRuns[display->textRunCount].offset, false);
} else {
EsElementSetDisabled(inspector->contentTextbox, true);
}
} else {
EsElementSetDisabled(inspector->contentTextbox, true);
}
}
int InspectorElementListCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int column = message->getContent.columnID, index = message->getContent.index;
EsAssert(index >= 0 && index < (int) inspector->elements.Length());
InspectorElementEntry *entry = &inspector->elements[index];
if (column == 0) {
EsBufferFormat(message->getContent.buffer, "%z", entry->element->cName);
} else if (column == 1) {
EsBufferFormat(message->getContent.buffer, "%R", entry->element->GetWindowBounds(false));
} else if (column == 2) {
EsMessage m = *message;
m.type = ES_MSG_GET_INSPECTOR_INFORMATION;
EsMessageSend(entry->element, &m);
}
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_GET_INDENT) {
message->getIndent.indent = inspector->elements[message->getIndent.index].depth;
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) {
message->createItem.item->messageUser = InspectorElementItemCallback;
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_SELECT) {
if (inspector->selectedElement != -1) {
inspector->elements[inspector->selectedElement].element->state &= ~UI_STATE_INSPECTING;
}
inspector->selectedElement = message->selectItem.isSelected ? message->selectItem.index : -1;
if (inspector->selectedElement != -1) {
EsElement *e = inspector->elements[inspector->selectedElement].element;
e->state |= UI_STATE_INSPECTING;
InspectorNotifyElementEvent(e, nullptr, "Viewing events from '%z'.\n", e->cName);
}
InspectorUpdateEditor(inspector);
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) {
message->selectItem.isSelected = message->selectItem.index == inspector->selectedElement;
return ES_HANDLED;
}
return 0;
}
int InspectorContentTextboxCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
size_t newContentBytes;
char *newContent = EsTextboxGetContents(inspector->contentTextbox, &newContentBytes);
EsElement *e = inspector->elements[inspector->selectedElement].element;
if (e->messageClass == ProcessButtonMessage) {
EsButton *button = (EsButton *) e;
HeapDuplicate((void **) &button->label, &button->labelBytes, newContent, newContentBytes);
} else if (e->messageClass == ProcessTextDisplayMessage) {
EsTextDisplay *display = (EsTextDisplay *) e;
EsTextDisplaySetContents(display, newContent, newContentBytes);
} else {
EsAssert(false);
}
EsElementUpdateContentSize(e);
if (e->parent) EsElementUpdateContentSize(e->parent);
EsHeapFree(newContent);
return ES_HANDLED;
}
return 0;
}
int InspectorTextboxCategoryFilterCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_TEXTBOX_UPDATED) {
EsHeapFree(inspector->cCategoryFilter);
inspector->cCategoryFilter = EsTextboxGetContents((EsTextbox *) element);
}
return 0;
}
InspectorWindow *InspectorGet(EsElement *element) {
if (!element->window || !element->instance) return nullptr;
APIInstance *instance = (APIInstance *) element->instance->_private;
InspectorWindow *inspector = instance->attachedInspector;
if (!inspector || inspector->instance->window != element->window) return nullptr;
return inspector;
}
void InspectorNotifyElementEvent(EsElement *element, const char *cCategory, const char *cFormat, ...) {
if (~element->state & UI_STATE_INSPECTING) return;
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
if (inspector->cCategoryFilter && inspector->cCategoryFilter[0] && cCategory && EsCRTstrcmp(cCategory, inspector->cCategoryFilter)) return;
va_list arguments;
va_start(arguments, cFormat);
char _buffer[256];
EsBuffer buffer = { .out = (uint8_t *) _buffer, .bytes = sizeof(_buffer) };
if (cCategory) EsBufferFormat(&buffer, "%z: ", cCategory);
EsBufferFormatV(&buffer, cFormat, arguments);
va_end(arguments);
EsListViewFixedItemInsert(inspector->listEvents, _buffer, buffer.position);
EsListViewScrollToEnd(inspector->listEvents);
}
void InspectorNotifyElementContentChanged(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
EsListViewInvalidateContent(inspector->elementList, 0, i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementMoved(EsElement *element, EsRectangle takenBounds) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
inspector->elements[i].takenBounds = takenBounds;
inspector->elements[i].givenBounds = takenBounds; // TODO.
EsListViewInvalidateContent(inspector->elementList, 0, i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementDestroyed(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
if (inspector->selectedElement == (intptr_t) i) {
inspector->selectedElement = -1;
InspectorUpdateEditor(inspector);
} else if (inspector->selectedElement > (intptr_t) i) {
inspector->selectedElement--;
}
EsListViewRemove(inspector->elementList, 0, i, 1);
inspector->elements.Delete(i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementCreated(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
ptrdiff_t indexInParent = -1;
for (uintptr_t i = 0; i < element->parent->children.Length(); i++) {
if (element->parent->children[i] == element) {
indexInParent = i;
break;
}
}
EsAssert(indexInParent != -1);
ptrdiff_t insertAfterIndex = -1;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (indexInParent == 0) {
if (inspector->elements[i].element == element->parent) {
insertAfterIndex = i;
break;
}
} else {
if (inspector->elements[i].element == element->parent->children[indexInParent - 1]) {
insertAfterIndex = i;
int baseDepth = inspector->elements[i++].depth;
for (; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].depth > baseDepth) {
insertAfterIndex++;
} else {
break;
}
}
break;
}
}
}
EsAssert(insertAfterIndex != -1);
int depth = 0;
EsElement *ancestor = element->parent;
while (ancestor) {
depth++;
ancestor = ancestor->parent;
}
if (inspector->selectedElement > insertAfterIndex) {
inspector->selectedElement++;
}
InspectorElementEntry entry;
entry.element = element;
entry.depth = depth;
inspector->elements.Insert(entry, insertAfterIndex + 1);
EsListViewInsert(inspector->elementList, 0, insertAfterIndex + 1, 1);
}
void InspectorFindElementsRecursively(InspectorWindow *inspector, EsElement *element, int depth) {
InspectorElementEntry entry = {};
entry.element = element;
entry.depth = depth;
inspector->elements.Add(entry);
for (uintptr_t i = 0; i < element->children.Length(); i++) {
InspectorFindElementsRecursively(inspector, element->children[i], depth + 1);
}
}
void InspectorRefreshElementList(InspectorWindow *inspector) {
EsListViewRemoveAll(inspector->elementList, 0);
inspector->elements.Free();
InspectorFindElementsRecursively(inspector, inspector->instance->window, 0);
EsListViewInsert(inspector->elementList, 0, 0, inspector->elements.Length());
}
void InspectorNotifyElementPainted(EsElement *element, EsPainter *painter) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
InspectorElementEntry *entry = inspector->hoveredElement.element ? &inspector->hoveredElement : nullptr;
if (!entry) return;
EsRectangle bounds = ES_RECT_4(painter->offsetX, painter->offsetX + painter->width,
painter->offsetY, painter->offsetY + painter->height);
if (entry->element == element) {
EsDrawRectangle(painter, bounds, 0x607F7FFF, 0x60FFFF7F, element->style->insets);
} else if (entry->element->parent == element) {
if ((element->flags & ES_CELL_FILL) != ES_CELL_FILL) {
EsRectangle rectangle = entry->givenBounds;
rectangle.l += bounds.l, rectangle.r += bounds.l;
rectangle.t += bounds.t, rectangle.b += bounds.t;
// EsDrawBlock(painter, rectangle, 0x20FF7FFF);
}
}
}
#define INSPECTOR_ALIGN_COMMAND(name, clear, set, toggle) \
void name (EsInstance *instance, EsElement *, EsCommand *) { \
InspectorWindow *inspector = (InspectorWindow *) instance; \
EsElement *e = inspector->elements[inspector->selectedElement].element; \
if (toggle) e->flags ^= set; \
else { e->flags &= ~(clear); e->flags |= set; } \
EsElementUpdateContentSize(e); \
if (e->parent) EsElementUpdateContentSize(e->parent); \
inspector->elementList->Repaint(true); \
InspectorUpdateEditor(inspector); \
}
INSPECTOR_ALIGN_COMMAND(InspectorHAlignLeft, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignCenter, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignRight, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_RIGHT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignExpand, 0, ES_CELL_H_EXPAND, true);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignShrink, 0, ES_CELL_H_SHRINK, true);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignPush, 0, ES_CELL_H_PUSH, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignTop, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignCenter, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignBottom, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_BOTTOM, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignExpand, 0, ES_CELL_V_EXPAND, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignShrink, 0, ES_CELL_V_SHRINK, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignPush, 0, ES_CELL_V_PUSH, true);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionLeft, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionRight, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionUp, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_REVERSE, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionDown, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, 0, false);
void InspectorVisualizeRepaints(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizeRepaints = !window->visualizeRepaints;
EsButtonSetCheck(inspector->visualizeRepaints, window->visualizeRepaints ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
}
void InspectorVisualizePaintSteps(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizePaintSteps = !window->visualizePaintSteps;
EsButtonSetCheck(inspector->visualizePaintSteps, window->visualizePaintSteps ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
}
void InspectorVisualizeLayoutBounds(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizeLayoutBounds = !window->visualizeLayoutBounds;
EsButtonSetCheck(inspector->visualizeLayoutBounds, window->visualizeLayoutBounds ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
EsElementRepaint(window);
}
void InspectorAddElement2(EsMenu *menu, EsGeneric context) {
InspectorWindow *inspector = (InspectorWindow *) menu->instance;
if (inspector->selectedElement == -1) return;
EsElement *e = inspector->elements[inspector->selectedElement].element;
int asSibling = context.u & 0x80;
context.u &= ~0x80;
if (asSibling) {
EsElementInsertAfter(e);
e = e->parent;
}
if (context.u == 1) {
EsButtonCreate(e);
} else if (context.u == 2) {
EsPanelCreate(e);
} else if (context.u == 3) {
EsSpacerCreate(e);
} else if (context.u == 4) {
EsTextboxCreate(e);
} else if (context.u == 5) {
EsTextDisplayCreate(e);
}
}
void InspectorAddElement(EsInstance *, EsElement *element, EsCommand *) {
EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT);
EsMenuAddItem(menu, 0, "Add button", -1, InspectorAddElement2, element->userData.u | 1);
EsMenuAddItem(menu, 0, "Add panel", -1, InspectorAddElement2, element->userData.u | 2);
EsMenuAddItem(menu, 0, "Add spacer", -1, InspectorAddElement2, element->userData.u | 3);
EsMenuAddItem(menu, 0, "Add textbox", -1, InspectorAddElement2, element->userData.u | 4);
EsMenuAddItem(menu, 0, "Add text display", -1, InspectorAddElement2, element->userData.u | 5);
EsMenuShow(menu);
}
void InspectorSetup(EsWindow *window) {
InspectorWindow *inspector = (InspectorWindow *) EsHeapAllocate(sizeof(InspectorWindow), true); // TODO Freeing this.
inspector->window = window;
InstanceSetup(inspector);
EsInstanceOpenReference(inspector);
inspector->instance = window->instance;
window->instance = inspector;
inspector->selectedElement = -1;
EsSplitter *splitter = EsSplitterCreate(window, ES_CELL_FILL | ES_SPLITTER_VERTICAL);
EsPanel *panel1 = EsPanelCreate(splitter, ES_CELL_FILL, ES_STYLE_PANEL_FILLED);
EsPanel *panel2 = EsPanelCreate(splitter, ES_CELL_FILL, ES_STYLE_PANEL_FILLED);
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
inspector->visualizeRepaints = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize repaints");
EsButtonOnCommand(inspector->visualizeRepaints, InspectorVisualizeRepaints);
inspector->visualizeLayoutBounds = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize layout bounds");
EsButtonOnCommand(inspector->visualizeLayoutBounds, InspectorVisualizeLayoutBounds);
inspector->visualizePaintSteps = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize paint steps");
EsButtonOnCommand(inspector->visualizePaintSteps, InspectorVisualizePaintSteps);
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
}
inspector->elementList = EsListViewCreate(panel1, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT);
inspector->elementList->messageUser = InspectorElementListCallback;
EsListViewRegisterColumn(inspector->elementList, 0, "Name", -1, 0, 300);
EsListViewRegisterColumn(inspector->elementList, 1, "Bounds", -1, 0, 200);
EsListViewRegisterColumn(inspector->elementList, 2, "Information", -1, 0, 200);
EsListViewAddAllColumns(inspector->elementList);
EsListViewInsertGroup(inspector->elementList, 0);
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Horizontal:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->alignH[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[0], ES_ICON_ALIGN_HORIZONTAL_LEFT);
EsButtonOnCommand(inspector->alignH[0], InspectorHAlignLeft);
inspector->alignH[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[1], ES_ICON_ALIGN_HORIZONTAL_CENTER);
EsButtonOnCommand(inspector->alignH[1], InspectorHAlignCenter);
inspector->alignH[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[2], ES_ICON_ALIGN_HORIZONTAL_RIGHT);
EsButtonOnCommand(inspector->alignH[2], InspectorHAlignRight);
inspector->alignH[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand");
EsButtonOnCommand(inspector->alignH[3], InspectorHAlignExpand);
inspector->alignH[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink");
EsButtonOnCommand(inspector->alignH[4], InspectorHAlignShrink);
inspector->alignH[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push");
EsButtonOnCommand(inspector->alignH[5], InspectorHAlignPush);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Vertical:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->alignV[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[0], ES_ICON_ALIGN_VERTICAL_TOP);
EsButtonOnCommand(inspector->alignV[0], InspectorVAlignTop);
inspector->alignV[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[1], ES_ICON_ALIGN_VERTICAL_CENTER);
EsButtonOnCommand(inspector->alignV[1], InspectorVAlignCenter);
inspector->alignV[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[2], ES_ICON_ALIGN_VERTICAL_BOTTOM);
EsButtonOnCommand(inspector->alignV[2], InspectorVAlignBottom);
inspector->alignV[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand");
EsButtonOnCommand(inspector->alignV[3], InspectorVAlignExpand);
inspector->alignV[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink");
EsButtonOnCommand(inspector->alignV[4], InspectorVAlignShrink);
inspector->alignV[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push");
EsButtonOnCommand(inspector->alignV[5], InspectorVAlignPush);
}
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Stack:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->direction[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[0], ES_ICON_GO_PREVIOUS);
EsButtonOnCommand(inspector->direction[0], InspectorDirectionLeft);
inspector->direction[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[1], ES_ICON_GO_NEXT);
EsButtonOnCommand(inspector->direction[1], InspectorDirectionRight);
inspector->direction[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[2], ES_ICON_GO_UP);
EsButtonOnCommand(inspector->direction[2], InspectorDirectionUp);
inspector->direction[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[3], ES_ICON_GO_DOWN);
EsButtonOnCommand(inspector->direction[3], InspectorDirectionDown);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0);
inspector->addChildButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add child... ");
EsButtonOnCommand(inspector->addChildButton, InspectorAddElement);
inspector->addSiblingButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add sibling... ");
inspector->addSiblingButton->userData.i = 0x80;
EsButtonOnCommand(inspector->addSiblingButton, InspectorAddElement);
}
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Content:");
inspector->contentTextbox = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED | ES_TEXTBOX_EDIT_BASED);
inspector->contentTextbox->messageUser = InspectorContentTextboxCallback;
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Event category filter:");
inspector->textboxCategoryFilter = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED);
inspector->textboxCategoryFilter->messageUser = InspectorTextboxCategoryFilterCallback;
}
{
inspector->listEvents = EsListViewCreate(panel2, ES_CELL_FILL | ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
}
InspectorRefreshElementList(inspector);
APIInstance *instance = (APIInstance *) inspector->instance->_private;
instance->attachedInspector = inspector;
}

602
desktop/inspector.cpp Normal file
View File

@ -0,0 +1,602 @@
struct InspectorElementEntry {
EsElement *element;
EsRectangle takenBounds, givenBounds;
int depth;
};
struct InspectorWindow : EsInstance {
EsInstance *instance; // The instance being inspected.
EsListView *elementList;
Array<InspectorElementEntry> elements; // TODO This is being leaked.
InspectorElementEntry hoveredElement;
char *cCategoryFilter;
intptr_t selectedElement;
EsButton *alignH[6];
EsButton *alignV[6];
EsButton *direction[4];
EsTextbox *contentTextbox;
EsButton *addChildButton;
EsButton *addSiblingButton;
EsButton *visualizeRepaints;
EsButton *visualizeLayoutBounds;
EsButton *visualizePaintSteps;
EsListView *listEvents;
EsTextbox *textboxCategoryFilter;
};
int InspectorElementItemCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_HOVERED_START) {
InspectorElementEntry *entry = &inspector->elements[EsListViewGetIndexFromItem(element)];
if (entry->element->parent) entry->element->parent->Repaint(true);
else entry->element->Repaint(true);
inspector->hoveredElement = *entry;
} else if (message->type == ES_MSG_HOVERED_END || message->type == ES_MSG_DESTROY) {
EsListViewIndex index = EsListViewGetIndexFromItem(element);
InspectorElementEntry *entry = &inspector->elements[index];
if (entry->element->parent) entry->element->parent->Repaint(true);
else entry->element->Repaint(true);
inspector->hoveredElement = {};
}
return 0;
}
void InspectorUpdateEditor(InspectorWindow *inspector) {
EsElement *e = inspector->selectedElement == -1 ? nullptr : inspector->elements[inspector->selectedElement].element;
bool isStack = e && e->messageClass == ProcessPanelMessage && !(e->flags & (ES_PANEL_Z_STACK | ES_PANEL_TABLE | ES_PANEL_SWITCHER));
bool alignHLeft = e ? (e->flags & ES_CELL_H_LEFT) : false, alignHRight = e ? (e->flags & ES_CELL_H_RIGHT) : false;
bool alignHExpand = e ? (e->flags & ES_CELL_H_EXPAND) : false, alignHShrink = e ? (e->flags & ES_CELL_H_SHRINK) : false;
bool alignHPush = e ? (e->flags & ES_CELL_H_PUSH) : false;
bool alignVTop = e ? (e->flags & ES_CELL_V_TOP) : false, alignVBottom = e ? (e->flags & ES_CELL_V_BOTTOM) : false;
bool alignVExpand = e ? (e->flags & ES_CELL_V_EXPAND) : false, alignVShrink = e ? (e->flags & ES_CELL_V_SHRINK) : false;
bool alignVPush = e ? (e->flags & ES_CELL_V_PUSH) : false;
bool stackHorizontal = isStack && (e->flags & ES_PANEL_HORIZONTAL);
bool stackReverse = isStack && (e->flags & ES_PANEL_REVERSE);
EsButtonSetCheck(inspector->alignH[0], (EsCheckState) (e && alignHLeft && !alignHRight), false);
EsButtonSetCheck(inspector->alignH[1], (EsCheckState) (e && alignHLeft == alignHRight), false);
EsButtonSetCheck(inspector->alignH[2], (EsCheckState) (e && !alignHLeft && alignHRight), false);
EsButtonSetCheck(inspector->alignH[3], (EsCheckState) (e && alignHExpand), false);
EsButtonSetCheck(inspector->alignH[4], (EsCheckState) (e && alignHShrink), false);
EsButtonSetCheck(inspector->alignH[5], (EsCheckState) (e && alignHPush), false);
EsButtonSetCheck(inspector->alignV[0], (EsCheckState) (e && alignVTop && !alignVBottom), false);
EsButtonSetCheck(inspector->alignV[1], (EsCheckState) (e && alignVTop == alignVBottom), false);
EsButtonSetCheck(inspector->alignV[2], (EsCheckState) (e && !alignVTop && alignVBottom), false);
EsButtonSetCheck(inspector->alignV[3], (EsCheckState) (e && alignVExpand), false);
EsButtonSetCheck(inspector->alignV[4], (EsCheckState) (e && alignVShrink), false);
EsButtonSetCheck(inspector->alignV[5], (EsCheckState) (e && alignVPush), false);
EsButtonSetCheck(inspector->direction[0], (EsCheckState) (isStack && stackHorizontal && stackReverse), false);
EsButtonSetCheck(inspector->direction[1], (EsCheckState) (isStack && stackHorizontal && !stackReverse), false);
EsButtonSetCheck(inspector->direction[2], (EsCheckState) (isStack && !stackHorizontal && stackReverse), false);
EsButtonSetCheck(inspector->direction[3], (EsCheckState) (isStack && !stackHorizontal && !stackReverse), false);
EsElementSetDisabled(inspector->alignH[0], !e);
EsElementSetDisabled(inspector->alignH[1], !e);
EsElementSetDisabled(inspector->alignH[2], !e);
EsElementSetDisabled(inspector->alignH[3], !e);
EsElementSetDisabled(inspector->alignH[4], !e);
EsElementSetDisabled(inspector->alignH[5], !e);
EsElementSetDisabled(inspector->alignV[0], !e);
EsElementSetDisabled(inspector->alignV[1], !e);
EsElementSetDisabled(inspector->alignV[2], !e);
EsElementSetDisabled(inspector->alignV[3], !e);
EsElementSetDisabled(inspector->alignV[4], !e);
EsElementSetDisabled(inspector->alignV[5], !e);
EsElementSetDisabled(inspector->direction[0], !isStack);
EsElementSetDisabled(inspector->direction[1], !isStack);
EsElementSetDisabled(inspector->direction[2], !isStack);
EsElementSetDisabled(inspector->direction[3], !isStack);
EsElementSetDisabled(inspector->addChildButton, !isStack);
EsElementSetDisabled(inspector->addSiblingButton, !e || !e->parent);
EsElementSetDisabled(inspector->textboxCategoryFilter, !e);
EsTextboxSelectAll(inspector->contentTextbox);
EsTextboxInsert(inspector->contentTextbox, "", 0, false);
if (e) {
#if 0
for (uintptr_t i = 0; i < sizeof(builtinStyles) / sizeof(builtinStyles[0]); i++) {
if (e->currentStyleKey.partHash == CalculateCRC64(EsLiteral(builtinStyles[i]))) {
EsTextboxInsert(inspector->styleTextbox, builtinStyles[i], -1, false);
break;
}
}
#endif
if (e->messageClass == ProcessButtonMessage) {
EsButton *button = (EsButton *) e;
EsElementSetDisabled(inspector->contentTextbox, false);
EsTextboxInsert(inspector->contentTextbox, button->label, button->labelBytes, false);
} else if (e->messageClass == ProcessTextDisplayMessage) {
EsTextDisplay *display = (EsTextDisplay *) e;
EsElementSetDisabled(inspector->contentTextbox, false);
EsTextboxInsert(inspector->contentTextbox, display->contents, display->textRuns[display->textRunCount].offset, false);
} else {
EsElementSetDisabled(inspector->contentTextbox, true);
}
} else {
EsElementSetDisabled(inspector->contentTextbox, true);
}
}
int InspectorElementListCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int column = message->getContent.columnID, index = message->getContent.index;
EsAssert(index >= 0 && index < (int) inspector->elements.Length());
InspectorElementEntry *entry = &inspector->elements[index];
if (column == 0) {
EsBufferFormat(message->getContent.buffer, "%z", entry->element->cName);
} else if (column == 1) {
EsBufferFormat(message->getContent.buffer, "%R", entry->element->GetWindowBounds(false));
} else if (column == 2) {
EsMessage m = *message;
m.type = ES_MSG_GET_INSPECTOR_INFORMATION;
EsMessageSend(entry->element, &m);
}
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_GET_INDENT) {
message->getIndent.indent = inspector->elements[message->getIndent.index].depth;
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) {
message->createItem.item->messageUser = InspectorElementItemCallback;
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_SELECT) {
if (inspector->selectedElement != -1) {
inspector->elements[inspector->selectedElement].element->state &= ~UI_STATE_INSPECTING;
}
inspector->selectedElement = message->selectItem.isSelected ? message->selectItem.index : -1;
if (inspector->selectedElement != -1) {
EsElement *e = inspector->elements[inspector->selectedElement].element;
e->state |= UI_STATE_INSPECTING;
InspectorNotifyElementEvent(e, nullptr, "Viewing events from '%z'.\n", e->cName);
}
InspectorUpdateEditor(inspector);
return ES_HANDLED;
} else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) {
message->selectItem.isSelected = message->selectItem.index == inspector->selectedElement;
return ES_HANDLED;
}
return 0;
}
int InspectorContentTextboxCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
size_t newContentBytes;
char *newContent = EsTextboxGetContents(inspector->contentTextbox, &newContentBytes);
EsElement *e = inspector->elements[inspector->selectedElement].element;
if (e->messageClass == ProcessButtonMessage) {
EsButton *button = (EsButton *) e;
HeapDuplicate((void **) &button->label, &button->labelBytes, newContent, newContentBytes);
} else if (e->messageClass == ProcessTextDisplayMessage) {
EsTextDisplay *display = (EsTextDisplay *) e;
EsTextDisplaySetContents(display, newContent, newContentBytes);
} else {
EsAssert(false);
}
EsElementUpdateContentSize(e);
if (e->parent) EsElementUpdateContentSize(e->parent);
EsHeapFree(newContent);
return ES_HANDLED;
}
return 0;
}
int InspectorTextboxCategoryFilterCallback(EsElement *element, EsMessage *message) {
InspectorWindow *inspector = (InspectorWindow *) element->instance;
if (message->type == ES_MSG_TEXTBOX_UPDATED) {
EsHeapFree(inspector->cCategoryFilter);
inspector->cCategoryFilter = EsTextboxGetContents((EsTextbox *) element);
}
return 0;
}
InspectorWindow *InspectorGet(EsElement *element) {
if (!element->window || !element->instance) return nullptr;
APIInstance *instance = (APIInstance *) element->instance->_private;
InspectorWindow *inspector = instance->attachedInspector;
if (!inspector || inspector->instance->window != element->window) return nullptr;
return inspector;
}
void InspectorNotifyElementEvent(EsElement *element, const char *cCategory, const char *cFormat, ...) {
if (~element->state & UI_STATE_INSPECTING) return;
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
if (inspector->cCategoryFilter && inspector->cCategoryFilter[0] && cCategory && EsCRTstrcmp(cCategory, inspector->cCategoryFilter)) return;
va_list arguments;
va_start(arguments, cFormat);
char _buffer[256];
EsBuffer buffer = { .out = (uint8_t *) _buffer, .bytes = sizeof(_buffer) };
if (cCategory) EsBufferFormat(&buffer, "%z: ", cCategory);
EsBufferFormatV(&buffer, cFormat, arguments);
va_end(arguments);
EsListViewFixedItemInsert(inspector->listEvents, _buffer, buffer.position);
EsListViewScrollToEnd(inspector->listEvents);
}
void InspectorNotifyElementContentChanged(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
EsListViewInvalidateContent(inspector->elementList, 0, i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementMoved(EsElement *element, EsRectangle takenBounds) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
inspector->elements[i].takenBounds = takenBounds;
inspector->elements[i].givenBounds = takenBounds; // TODO.
EsListViewInvalidateContent(inspector->elementList, 0, i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementDestroyed(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].element == element) {
if (inspector->selectedElement == (intptr_t) i) {
inspector->selectedElement = -1;
InspectorUpdateEditor(inspector);
} else if (inspector->selectedElement > (intptr_t) i) {
inspector->selectedElement--;
}
EsListViewRemove(inspector->elementList, 0, i, 1);
inspector->elements.Delete(i);
return;
}
}
EsAssert(false);
}
void InspectorNotifyElementCreated(EsElement *element) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
ptrdiff_t indexInParent = -1;
for (uintptr_t i = 0; i < element->parent->children.Length(); i++) {
if (element->parent->children[i] == element) {
indexInParent = i;
break;
}
}
EsAssert(indexInParent != -1);
ptrdiff_t insertAfterIndex = -1;
for (uintptr_t i = 0; i < inspector->elements.Length(); i++) {
if (indexInParent == 0) {
if (inspector->elements[i].element == element->parent) {
insertAfterIndex = i;
break;
}
} else {
if (inspector->elements[i].element == element->parent->children[indexInParent - 1]) {
insertAfterIndex = i;
int baseDepth = inspector->elements[i++].depth;
for (; i < inspector->elements.Length(); i++) {
if (inspector->elements[i].depth > baseDepth) {
insertAfterIndex++;
} else {
break;
}
}
break;
}
}
}
EsAssert(insertAfterIndex != -1);
int depth = 0;
EsElement *ancestor = element->parent;
while (ancestor) {
depth++;
ancestor = ancestor->parent;
}
if (inspector->selectedElement > insertAfterIndex) {
inspector->selectedElement++;
}
InspectorElementEntry entry;
entry.element = element;
entry.depth = depth;
inspector->elements.Insert(entry, insertAfterIndex + 1);
EsListViewInsert(inspector->elementList, 0, insertAfterIndex + 1, 1);
}
void InspectorFindElementsRecursively(InspectorWindow *inspector, EsElement *element, int depth) {
InspectorElementEntry entry = {};
entry.element = element;
entry.depth = depth;
inspector->elements.Add(entry);
for (uintptr_t i = 0; i < element->children.Length(); i++) {
InspectorFindElementsRecursively(inspector, element->children[i], depth + 1);
}
}
void InspectorRefreshElementList(InspectorWindow *inspector) {
EsListViewRemoveAll(inspector->elementList, 0);
inspector->elements.Free();
InspectorFindElementsRecursively(inspector, inspector->instance->window, 0);
EsListViewInsert(inspector->elementList, 0, 0, inspector->elements.Length());
}
void InspectorNotifyElementPainted(EsElement *element, EsPainter *painter) {
InspectorWindow *inspector = InspectorGet(element);
if (!inspector) return;
InspectorElementEntry *entry = inspector->hoveredElement.element ? &inspector->hoveredElement : nullptr;
if (!entry) return;
EsRectangle bounds = ES_RECT_4(painter->offsetX, painter->offsetX + painter->width,
painter->offsetY, painter->offsetY + painter->height);
if (entry->element == element) {
EsDrawRectangle(painter, bounds, 0x607F7FFF, 0x60FFFF7F, element->style->insets);
} else if (entry->element->parent == element) {
if ((element->flags & ES_CELL_FILL) != ES_CELL_FILL) {
EsRectangle rectangle = entry->givenBounds;
rectangle.l += bounds.l, rectangle.r += bounds.l;
rectangle.t += bounds.t, rectangle.b += bounds.t;
// EsDrawBlock(painter, rectangle, 0x20FF7FFF);
}
}
}
#define INSPECTOR_ALIGN_COMMAND(name, clear, set, toggle) \
void name (EsInstance *instance, EsElement *, EsCommand *) { \
InspectorWindow *inspector = (InspectorWindow *) instance; \
EsElement *e = inspector->elements[inspector->selectedElement].element; \
if (toggle) e->flags ^= set; \
else { e->flags &= ~(clear); e->flags |= set; } \
EsElementUpdateContentSize(e); \
if (e->parent) EsElementUpdateContentSize(e->parent); \
inspector->elementList->Repaint(true); \
InspectorUpdateEditor(inspector); \
}
INSPECTOR_ALIGN_COMMAND(InspectorHAlignLeft, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignCenter, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignRight, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_RIGHT, false);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignExpand, 0, ES_CELL_H_EXPAND, true);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignShrink, 0, ES_CELL_H_SHRINK, true);
INSPECTOR_ALIGN_COMMAND(InspectorHAlignPush, 0, ES_CELL_H_PUSH, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignTop, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignCenter, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignBottom, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_BOTTOM, false);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignExpand, 0, ES_CELL_V_EXPAND, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignShrink, 0, ES_CELL_V_SHRINK, true);
INSPECTOR_ALIGN_COMMAND(InspectorVAlignPush, 0, ES_CELL_V_PUSH, true);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionLeft, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionRight, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionUp, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE,
ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_REVERSE, false);
INSPECTOR_ALIGN_COMMAND(InspectorDirectionDown, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, 0, false);
void InspectorVisualizeRepaints(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizeRepaints = !window->visualizeRepaints;
EsButtonSetCheck(inspector->visualizeRepaints, window->visualizeRepaints ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
}
void InspectorVisualizePaintSteps(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizePaintSteps = !window->visualizePaintSteps;
EsButtonSetCheck(inspector->visualizePaintSteps, window->visualizePaintSteps ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
}
void InspectorVisualizeLayoutBounds(EsInstance *instance, EsElement *, EsCommand *) {
InspectorWindow *inspector = (InspectorWindow *) instance;
EsWindow *window = inspector->instance->window;
window->visualizeLayoutBounds = !window->visualizeLayoutBounds;
EsButtonSetCheck(inspector->visualizeLayoutBounds, window->visualizeLayoutBounds ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
EsElementRepaint(window);
}
void InspectorAddElement2(EsMenu *menu, EsGeneric context) {
InspectorWindow *inspector = (InspectorWindow *) menu->instance;
if (inspector->selectedElement == -1) return;
EsElement *e = inspector->elements[inspector->selectedElement].element;
int asSibling = context.u & 0x80;
context.u &= ~0x80;
if (asSibling) {
EsElementInsertAfter(e);
e = e->parent;
}
if (context.u == 1) {
EsButtonCreate(e);
} else if (context.u == 2) {
EsPanelCreate(e);
} else if (context.u == 3) {
EsSpacerCreate(e);
} else if (context.u == 4) {
EsTextboxCreate(e);
} else if (context.u == 5) {
EsTextDisplayCreate(e);
}
}
void InspectorAddElement(EsInstance *, EsElement *element, EsCommand *) {
EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT);
EsMenuAddItem(menu, 0, "Add button", -1, InspectorAddElement2, element->userData.u | 1);
EsMenuAddItem(menu, 0, "Add panel", -1, InspectorAddElement2, element->userData.u | 2);
EsMenuAddItem(menu, 0, "Add spacer", -1, InspectorAddElement2, element->userData.u | 3);
EsMenuAddItem(menu, 0, "Add textbox", -1, InspectorAddElement2, element->userData.u | 4);
EsMenuAddItem(menu, 0, "Add text display", -1, InspectorAddElement2, element->userData.u | 5);
EsMenuShow(menu);
}
void InspectorSetup(EsWindow *window) {
InspectorWindow *inspector = (InspectorWindow *) EsHeapAllocate(sizeof(InspectorWindow), true); // TODO Freeing this.
inspector->window = window;
InstanceSetup(inspector);
EsInstanceOpenReference(inspector);
inspector->instance = window->instance;
window->instance = inspector;
inspector->selectedElement = -1;
EsSplitter *splitter = EsSplitterCreate(window, ES_CELL_FILL | ES_SPLITTER_VERTICAL);
EsPanel *panel1 = EsPanelCreate(splitter, ES_CELL_FILL, ES_STYLE_PANEL_FILLED);
EsPanel *panel2 = EsPanelCreate(splitter, ES_CELL_FILL, ES_STYLE_PANEL_FILLED);
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
inspector->visualizeRepaints = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize repaints");
EsButtonOnCommand(inspector->visualizeRepaints, InspectorVisualizeRepaints);
inspector->visualizeLayoutBounds = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize layout bounds");
EsButtonOnCommand(inspector->visualizeLayoutBounds, InspectorVisualizeLayoutBounds);
inspector->visualizePaintSteps = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize paint steps");
EsButtonOnCommand(inspector->visualizePaintSteps, InspectorVisualizePaintSteps);
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
}
inspector->elementList = EsListViewCreate(panel1, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT);
inspector->elementList->messageUser = InspectorElementListCallback;
EsListViewRegisterColumn(inspector->elementList, 0, "Name", -1, 0, 300);
EsListViewRegisterColumn(inspector->elementList, 1, "Bounds", -1, 0, 200);
EsListViewRegisterColumn(inspector->elementList, 2, "Information", -1, 0, 200);
EsListViewAddAllColumns(inspector->elementList);
EsListViewInsertGroup(inspector->elementList, 0);
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Horizontal:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->alignH[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[0], ES_ICON_ALIGN_HORIZONTAL_LEFT);
EsButtonOnCommand(inspector->alignH[0], InspectorHAlignLeft);
inspector->alignH[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[1], ES_ICON_ALIGN_HORIZONTAL_CENTER);
EsButtonOnCommand(inspector->alignH[1], InspectorHAlignCenter);
inspector->alignH[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignH[2], ES_ICON_ALIGN_HORIZONTAL_RIGHT);
EsButtonOnCommand(inspector->alignH[2], InspectorHAlignRight);
inspector->alignH[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand");
EsButtonOnCommand(inspector->alignH[3], InspectorHAlignExpand);
inspector->alignH[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink");
EsButtonOnCommand(inspector->alignH[4], InspectorHAlignShrink);
inspector->alignH[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push");
EsButtonOnCommand(inspector->alignH[5], InspectorHAlignPush);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Vertical:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->alignV[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[0], ES_ICON_ALIGN_VERTICAL_TOP);
EsButtonOnCommand(inspector->alignV[0], InspectorVAlignTop);
inspector->alignV[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[1], ES_ICON_ALIGN_VERTICAL_CENTER);
EsButtonOnCommand(inspector->alignV[1], InspectorVAlignCenter);
inspector->alignV[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->alignV[2], ES_ICON_ALIGN_VERTICAL_BOTTOM);
EsButtonOnCommand(inspector->alignV[2], InspectorVAlignBottom);
inspector->alignV[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand");
EsButtonOnCommand(inspector->alignV[3], InspectorVAlignExpand);
inspector->alignV[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink");
EsButtonOnCommand(inspector->alignV[4], InspectorVAlignShrink);
inspector->alignV[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push");
EsButtonOnCommand(inspector->alignV[5], InspectorVAlignPush);
}
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Stack:");
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
inspector->direction[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[0], ES_ICON_GO_PREVIOUS);
EsButtonOnCommand(inspector->direction[0], InspectorDirectionLeft);
inspector->direction[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[1], ES_ICON_GO_NEXT);
EsButtonOnCommand(inspector->direction[1], InspectorDirectionRight);
inspector->direction[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[2], ES_ICON_GO_UP);
EsButtonOnCommand(inspector->direction[2], InspectorDirectionUp);
inspector->direction[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED);
EsButtonSetIcon(inspector->direction[3], ES_ICON_GO_DOWN);
EsButtonOnCommand(inspector->direction[3], InspectorDirectionDown);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0);
inspector->addChildButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add child... ");
EsButtonOnCommand(inspector->addChildButton, InspectorAddElement);
inspector->addSiblingButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add sibling... ");
inspector->addSiblingButton->userData.i = 0x80;
EsButtonOnCommand(inspector->addSiblingButton, InspectorAddElement);
}
{
EsPanel *toolbar = EsPanelCreate(panel1, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Content:");
inspector->contentTextbox = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED | ES_TEXTBOX_EDIT_BASED);
inspector->contentTextbox->messageUser = InspectorContentTextboxCallback;
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0);
EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Event category filter:");
inspector->textboxCategoryFilter = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED);
inspector->textboxCategoryFilter->messageUser = InspectorTextboxCategoryFilterCallback;
}
{
inspector->listEvents = EsListViewCreate(panel2, ES_CELL_FILL | ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
}
InspectorRefreshElementList(inspector);
APIInstance *instance = (APIInstance *) inspector->instance->_private;
instance->attachedInspector = inspector;
}

File diff suppressed because it is too large Load Diff

2132
desktop/textbox.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -248,29 +248,6 @@ typedef struct ThemeHeader {
// Followed by array of ThemeStyles and then an array of ThemeConstants.
} ThemeHeader;
typedef struct BasicFontKerningEntry {
uint16_t leftGlyphIndex, rightGlyphIndex;
int16_t xAdvance;
} BasicFontKerningEntry;
typedef struct BasicFontGlyph {
uint32_t codepoint;
int16_t xAdvance, xOffset, yOffset;
uint16_t width, height;
uint16_t pointCount;
uint32_t offsetToPoints; // Cubic bezier points. Contains 3*pointCount-2 of (x,y) float pairs.
} BasicFontGlyph;
typedef struct BasicFontHeader {
#define BASIC_FONT_SIGNATURE (0x83259919)
uint32_t signature;
int32_t ascender, descender;
uint16_t glyphCount;
uint16_t kerningEntries;
// Followed by array of BasicFontGlyph.
// Followed by array of BasicFontKerningEntry.
} BasicFontHeader;
//////////////////////////////////////////
#define THEME_RECT_WIDTH(_r) ((_r).r - (_r).l)

Binary file not shown.

View File

@ -7,10 +7,11 @@
#define DEFINE_INTERFACE_STRING(name, text) static const char *interfaceString_ ## name = text;
#define INTERFACE_STRING(name) interfaceString_ ## name, -1
#define ELLIPSIS ""
#define HYPHENATION_POINT ""
#define ELLIPSIS "\u2026"
#define HYPHENATION_POINT "\u2027"
#define OPEN_SPEECH "\u201C"
#define CLOSE_SPEECH "\u201D"
#define SYSTEM_BRAND_SHORT "Essence"
// Common.

View File

@ -299,8 +299,7 @@ Option options[] = {
{ "Dependency.stb_image", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.stb_image_write", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.stb_sprintf", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.HarfBuzz", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.FreeType", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.FreeTypeAndHarfBuzz", OPTION_TYPE_BOOL, { .b = true } },
{ "Emulator.AHCI", OPTION_TYPE_BOOL, { .b = true } },
{ "Emulator.ATA", OPTION_TYPE_BOOL, { .b = false } },
{ "Emulator.NVMe", OPTION_TYPE_BOOL, { .b = false }, .warning = "Recent versions of Qemu have trouble booting from NVMe drives." },

View File

@ -160,7 +160,6 @@ bool verbose;
bool useColoredOutput;
bool forEmulator, bootUseVBE, noImportPOSIX;
bool systemBuild;
bool convertFonts = true;
bool hasNativeToolchain;
EsINIState *fontLines;
EsINIState *generalOptions;
@ -891,18 +890,7 @@ void OutputSystemConfiguration() {
char buffer[4096];
if (fontLines[i].key[0] == '.') {
if (convertFonts) {
#ifdef OS_ESSENCE
// TODO.
#else
snprintf(buffer, sizeof(buffer), "bin/designer --make-font \"res/Fonts/%s\" \"bin/%.*s.dat\"",
fontLines[i].value, (int) fontLines[i].valueBytes - 4, fontLines[i].value);
system(buffer);
FilePrintFormat(file, "%s=|Fonts:/%.*s.dat\n", fontLines[i].key, (int) fontLines[i].valueBytes - 4, fontLines[i].value);
#endif
} else {
FilePrintFormat(file, "%s=:%s\n", fontLines[i].key, fontLines[i].value);
}
FilePrintFormat(file, "%s=:%s\n", fontLines[i].key, fontLines[i].value);
} else {
size_t bytes = EsINIFormat(fontLines + i, buffer, sizeof(buffer));
FileWrite(file, bytes, buffer);
@ -1223,22 +1211,6 @@ void Install(const char *driveFile, uint64_t partitionSize, const char *partitio
ImportNode root = {};
CreateImportNode("root", &root);
// TODO Update this.
#if 0
if (convertFonts) {
ImportNode *fontsFolder = ImportNodeMakeDirectory(ImportNodeFindChild(&root, SYSTEM_FOLDER_NAME), "Fonts");
for (uintptr_t i = 0; i < arrlenu(fontLines); i++) {
if (fontLines[i].key[0] == '.') {
char source[4096], destination[4096];
snprintf(source, sizeof(source), "bin/%.*s.dat", (int) fontLines[i].valueBytes - 4, fontLines[i].value);
snprintf(destination, sizeof(destination), "%.*s.dat", (int) fontLines[i].valueBytes - 4, fontLines[i].value);
ImportNodeAddFile(fontsFolder, strdup(destination), strdup(source));
}
}
}
#endif
MountVolume();
Import(root, superblock.root);
UnmountVolume();
@ -1343,13 +1315,9 @@ int main(int argc, char **argv) {
strcat(commonCompileFlags, " -DUSE_STB_IMAGE_WRITE ");
} else if (0 == strcmp(s.key, "Dependency.stb_sprintf") && atoi(s.value)) {
strcat(commonCompileFlags, " -DUSE_STB_SPRINTF ");
} else if (0 == strcmp(s.key, "Dependency.HarfBuzz") && atoi(s.value)) {
strcat(apiLinkFlags2, " -lharfbuzz ");
strcat(commonCompileFlags, " -DUSE_HARFBUZZ ");
} else if (0 == strcmp(s.key, "Dependency.FreeType") && atoi(s.value)) {
strcat(apiLinkFlags2, " -lfreetype ");
strcat(commonCompileFlags, " -DUSE_FREETYPE ");
convertFonts = false;
} else if (0 == strcmp(s.key, "Dependency.FreeTypeAndHarfBuzz") && atoi(s.value)) {
strcat(apiLinkFlags2, " -lharfbuzz -lfreetype ");
strcat(commonCompileFlags, " -DUSE_FREETYPE_AND_HARFBUZZ ");
} else if (0 == strcmp(s.key, "Flag._ALWAYS_USE_VBE")) {
bootUseVBE = !!atoi(s.value);
} else if (0 == strcmp(s.key, "Flag.COM_OUTPUT") && atoi(s.value)) {

View File

@ -1,4 +1,3 @@
// TODO Required: final file format, line metrics, horizontally scrolling kerning editor.
// TODO Extensions: binary search, shifting glyphs in editor, undo/redo.
#define UI_IMPLEMENTATION
@ -14,6 +13,7 @@
typedef struct FileHeader {
uint16_t glyphCount;
uint8_t headerBytes, glyphHeaderBytes;
uint16_t yAscent, yDescent;
// Followed by glyphCount copies of FileGlyphHeader, sorted by codepoint.
} FileHeader;
@ -49,12 +49,14 @@ UIWindow *window;
UITabPane *tabPane;
UIElement *editor;
UIElement *kerning;
UITextbox *previewText;
UITextbox *previewText, *yAscentTextbox, *yDescentTextbox;
UIScrollBar *kerningHScroll, *kerningVScroll;
Glyph *glyphsArray;
size_t glyphCount;
intptr_t selectedGlyph = -1;
int selectedPixelX, selectedPixelY;
int selectedPairX, selectedPairY, selectedPairI, selectedPairJ;
int yAscent, yDescent;
char *path;
void Save(void *cp) {
@ -65,6 +67,8 @@ void Save(void *cp) {
.glyphCount = glyphCount,
.headerBytes = sizeof(FileHeader),
.glyphHeaderBytes = sizeof(FileGlyphHeader),
.yAscent = yAscent,
.yDescent = yDescent,
};
fwrite(&header, 1, sizeof(header), f);
@ -122,9 +126,12 @@ void Load() {
FILE *f = fopen(path, "rb");
if (f) {
FileHeader header;
fread(&header, 1, sizeof(header), f);
FileHeader header = { 0 };
fread(&header, 1, 4, f);
if (ferror(f)) goto end;
fseek(f, 0, SEEK_SET);
fread(&header, 1, header.headerBytes > sizeof(FileHeader) ? sizeof(FileHeader) : header.headerBytes, f);
fseek(f, header.headerBytes, SEEK_SET);
glyphCount = header.glyphCount;
glyphsArray = (Glyph *) calloc(glyphCount, sizeof(Glyph));
if (!glyphsArray) goto end;
@ -172,6 +179,12 @@ void Load() {
free(bits);
fseek(f, position, SEEK_SET);
}
char buffer[32];
snprintf(buffer, sizeof(buffer), "%d", header.yAscent);
UITextboxReplace(yAscentTextbox, buffer, -1, true);
snprintf(buffer, sizeof(buffer), "%d", header.yDescent);
UITextboxReplace(yDescentTextbox, buffer, -1, true);
end:;
@ -207,9 +220,9 @@ int CompareKernings(const void *_a, const void *_b) {
void AddGlyph(void *cp) {
char *number = NULL;
UIDialogShow(window, 0, "Enter the glyph number:\n%t\n%f%b", &number, "Add");
UIDialogShow(window, 0, "Enter the glyph number (base 16):\n%t\n%f%b", &number, "Add");
Glyph g = { 0 };
g.number = atoi(number);
g.number = strtol(number, NULL, 16);
free(number);
glyphsTable->itemCount = ++glyphCount;
glyphsArray = realloc(glyphsArray, sizeof(Glyph) * glyphCount);
@ -240,9 +253,13 @@ int GlyphsTableMessage(UIElement *element, UIMessage message, int di, void *dp)
m->isSelected = selectedGlyph == m->index;
if (m->column == 0) {
return snprintf(m->buffer, m->bufferBytes, "%c", glyphsArray[m->index].number);
if (glyphsArray[m->index].number < 256) {
return snprintf(m->buffer, m->bufferBytes, "%c", glyphsArray[m->index].number);
} else {
return 0;
}
} else if (m->column == 1) {
return snprintf(m->buffer, m->bufferBytes, "%d", glyphsArray[m->index].number);
return snprintf(m->buffer, m->bufferBytes, "U+%.4X", glyphsArray[m->index].number);
}
} else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_MOUSE_DRAG) {
int index = UITableHitTest((UITable *) element, element->window->cursorX, element->window->cursorY);
@ -301,10 +318,13 @@ int GetAdvance(int leftGlyph, int rightGlyph, bool *hasKerningEntry) {
}
void DrawPreviewText(UIPainter *painter, UIElement *element, Glyph *g) {
UIDrawBlock(painter, UI_RECT_4(element->clip.r - 100, element->clip.r, element->clip.t, element->clip.t + 50), 0xFFFFFFFF);
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t, element->bounds.t + 50), 0xFFFFFFFF);
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25 - yAscent, element->bounds.t + 26 - yAscent), 0xFF88FF88);
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25, element->bounds.t + 26), 0xFF88FF88);
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25 + yDescent, element->bounds.t + 26 + yDescent), 0xFF88FF88);
if (previewText->bytes == 0 && g) {
DrawGlyph(painter, g, element->clip.r - 100 + 5, element->clip.t + 25);
DrawGlyph(painter, g, element->bounds.r - 100 + 5, element->bounds.t + 25);
return;
}
@ -317,7 +337,7 @@ void DrawPreviewText(UIPainter *painter, UIElement *element, Glyph *g) {
for (uintptr_t j = 0; j < glyphCount; j++) {
if (glyphsArray[j].number == previewText->string[i]) {
if (previous != -1) px += GetAdvance(previous, j, NULL);
DrawGlyph(painter, &glyphsArray[j], element->clip.r - 100 + 5 + px, element->clip.t + 25);
DrawGlyph(painter, &glyphsArray[j], element->bounds.r - 100 + 5 + px, element->bounds.t + 25);
previous = j;
break;
}
@ -418,7 +438,7 @@ int KerningEditorMessage(UIElement *element, UIMessage message, int di, void *dp
UIPainter *painter = (UIPainter *) dp;
UIDrawBlock(painter, element->bounds, 0xD0D1D4);
int x = element->bounds.l + 20, y = element->bounds.t + 20;
int x = element->bounds.l + 20 - kerningHScroll->position, y = element->bounds.t + 20 - kerningVScroll->position;
selectedPairI = -1, selectedPairJ = -1;
@ -432,7 +452,7 @@ int KerningEditorMessage(UIElement *element, UIMessage message, int di, void *dp
UIRectangle border = UI_RECT_4(x - 5, x + 20, y - 15, y + 5);
if (hasKerningEntry) {
UIDrawBorder(painter, border, 0xFF0099FF, UI_RECT_1(1));
UIDrawBorder(painter, border, 0xFF0099FF, UI_RECT_1(2));
}
if (selectedPairX == (x - 20 - element->bounds.l) / 25 && selectedPairY == (y - 20 - element->bounds.t) / 20) {
@ -443,13 +463,29 @@ int KerningEditorMessage(UIElement *element, UIMessage message, int di, void *dp
x += 25;
}
x = element->bounds.l + 20;
x = element->bounds.l + 20 - kerningHScroll->position;
y += 20;
}
DrawPreviewText(painter, element, NULL);
} else if (message == UI_MSG_GET_HEIGHT) {
return 20 * glyphCount + 40;
} else if (message == UI_MSG_LAYOUT) {
{
kerningHScroll->maximum = 25 * glyphCount + 40;
kerningHScroll->page = UI_RECT_WIDTH(element->bounds);
UIRectangle scrollBarBounds = element->bounds;
scrollBarBounds.r = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * element->window->scale;
scrollBarBounds.t = scrollBarBounds.b - UI_SIZE_SCROLL_BAR * element->window->scale;
UIElementMove(&kerningHScroll->e, scrollBarBounds, true);
}
{
kerningVScroll->maximum = 20 * glyphCount + 40;
kerningVScroll->page = UI_RECT_HEIGHT(element->bounds);
UIRectangle scrollBarBounds = element->bounds;
scrollBarBounds.b = scrollBarBounds.b - UI_SIZE_SCROLL_BAR * element->window->scale;
scrollBarBounds.l = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * element->window->scale;
UIElementMove(&kerningVScroll->e, scrollBarBounds, true);
}
} else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_RIGHT_DOWN) {
int delta = message == UI_MSG_LEFT_DOWN ? 1 : -1;
@ -491,6 +527,10 @@ int KerningEditorMessage(UIElement *element, UIMessage message, int di, void *dp
selectedPairY = pairY;
UIElementRepaint(element, NULL);
}
} else if (message == UI_MSG_SCROLLED) {
UIElementRepaint(element, NULL);
} else if (message == UI_MSG_MOUSE_WHEEL) {
return UIElementMessage(&kerningVScroll->e, message, di, dp);
}
return 0;
@ -505,6 +545,17 @@ int PreviewTextMessage(UIElement *element, UIMessage message, int di, void *dp)
return 0;
}
int NumberTextboxMessage(UIElement *element, UIMessage message, int di, void *dp) {
if (message == UI_MSG_VALUE_CHANGED) {
UITextbox *textbox = (UITextbox *) element;
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.*s", (int) textbox->bytes, textbox->string);
*(int *) element->cp = atoi(buffer);
}
return 0;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <path to font file>\n", argv[0]);
@ -514,6 +565,7 @@ int main(int argc, char **argv) {
path = argv[1];
UIInitialise();
ui.theme = _uiThemeClassic;
window = UIWindowCreate(0, UI_ELEMENT_PARENT_PUSH, "Font Editor", 1024, 768);
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
@ -527,11 +579,27 @@ int main(int argc, char **argv) {
previewText->e.messageUser = PreviewTextMessage;
UIParentPop();
tabPane = UITabPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_V_FILL, "Select glyph\tEdit\tKerning");
tabPane = UITabPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_V_FILL, "Glyphs\tEdit\tKerning\tGeneral");
glyphsTable = UITableCreate(0, 0, "ASCII\tNumber");
glyphsTable->e.messageUser = GlyphsTableMessage;
editor = UIElementCreate(sizeof(UIElement), 0, 0, GlyphEditorMessage, "Glyph editor");
kerning = UIElementCreate(sizeof(UIElement), &UIPanelCreate(0, UI_PANEL_SCROLL)->e, UI_ELEMENT_H_FILL, KerningEditorMessage, "Kerning editor");
kerning = UIElementCreate(sizeof(UIElement), 0, 0, KerningEditorMessage, "Kerning editor");
kerningHScroll = UIScrollBarCreate(kerning, UI_SCROLL_BAR_HORIZONTAL);
kerningVScroll = UIScrollBarCreate(kerning, 0);
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_PANEL_SCROLL);
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING);
UILabelCreate(0, 0, "Y ascent:", -1);
yAscentTextbox = UITextboxCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0);
yAscentTextbox->e.cp = &yAscent;
yAscentTextbox->e.messageUser = NumberTextboxMessage;
UILabelCreate(0, 0, "Y descent:", -1);
yDescentTextbox = UITextboxCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0);
yDescentTextbox->e.cp = &yDescent;
yDescentTextbox->e.messageUser = NumberTextboxMessage;
UILabelCreate(0, 0, "The sum of the ascent and descent determine the line height.", -1);
UIParentPop();
UIParentPop();
UIWindowRegisterShortcut(window, (UIShortcut) { .code = UI_KEYCODE_LETTER('S'), .ctrl = true, .invoke = Save });