mirror of https://gitlab.com/nakst/essence
remove old text code; organize UI code
This commit is contained in:
parent
69f46115b6
commit
1f92f55e46
|
@ -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[] = {
|
||||
|
|
891
desktop/gui.cpp
891
desktop/gui.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
2822
desktop/text.cpp
2822
desktop/text.cpp
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
@ -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.
|
||||
|
|
|
@ -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." },
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
Loading…
Reference in New Issue