Created
December 13, 2021 20:03
-
-
Save memononen/fc11737ee78bbc5aeee0cb95c1c564b8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "milayout.h" | |
#include <stdlib.h> | |
#include <stdarg.h> | |
static float mi__minf(float a, float b) { return a < b ? a : b; } | |
static float mi__maxf(float a, float b) { return a > b ? a : b; } | |
static int mi__getGrow(MIbox* box, int main) | |
{ | |
return box->content.xy[main] > 0 ? box->grow : (box->grow | 1); | |
} | |
void miMeasureContent(MIbox* box) | |
{ | |
MIsize contained = { .width = 0.0f, .height = 0.0f }; | |
int main = box->layout.dir; | |
int cross = box->layout.dir ^ 1; | |
for (MIbox* child = box->child; child; child = child->next) { | |
miMeasureContent(child); | |
} | |
for (MIbox* child = box->child; child; child = child->next) { | |
contained.xy[main] += child->rect.size.xy[main] + (child->next ? box->layout.spacing : 0); | |
contained.xy[cross] = mi__maxf(contained.xy[cross], child->rect.size.xy[cross]); | |
} | |
box->rect.width = (box->content.width > 0.0f ? box->content.width : contained.width) + box->layout.pad.x * 2; | |
box->rect.height = (box->content.height > 0.0f ? box->content.height : contained.height) + box->layout.pad.y * 2; | |
} | |
static void mi__layoutBoxes(MIbox* box) | |
{ | |
int main = box->layout.dir; | |
int cross = box->layout.dir ^ 1; | |
float sizeSum = 0.0f; | |
int growSum = 0; | |
int numChild = 0; | |
for (MIbox* child = box->child; child; child = child->next) { | |
growSum += mi__getGrow(child, main); | |
sizeSum += child->rect.size.xy[main]; | |
if (child->next) sizeSum += box->layout.spacing; | |
numChild++; | |
} | |
if (!numChild) | |
return; | |
MIpoint orig = { | |
.x = box->rect.x + box->layout.pad.x, | |
.y = box->rect.y + box->layout.pad.y | |
}; | |
MIsize inner = { | |
.width = mi__maxf(0, box->rect.width - box->layout.pad.x * 2), | |
.height = mi__maxf(0, box->rect.height - box->layout.pad.y * 2) | |
}; | |
float avail = inner.xy[main] - sizeSum; | |
float scale = 1.0f; | |
float grow = 0.0f; | |
if (avail >= 0) { | |
// Extra space, figure out how to distribute | |
if (box->layout.pack == MI_FILL) { | |
// Grow or scale proportionally to fit. | |
if (growSum > 0) | |
grow = avail / (float)growSum; | |
else | |
scale = sizeSum > 0.f ? inner.xy[main] / sizeSum : 0.0f; | |
} else if (box->layout.pack == MI_START) { | |
orig.xy[main] += 0; | |
} else if (box->layout.pack == MI_CENTER) { | |
orig.xy[main] += avail/2; | |
} else if (box->layout.pack == MI_END) { | |
orig.xy[main] += avail; | |
} | |
} else { | |
// Shrink to fit | |
if (box->layout.overflow == MI_FIT) { | |
scale = sizeSum > 0.f ? inner.xy[main] / sizeSum : 0.0f; | |
} | |
} | |
for (MIbox* child = box->child; child; child = child->next) { | |
child->rect.pos = orig; | |
// Align cross | |
if (child->rect.size.xy[cross] >= inner.xy[cross]) { | |
child->rect.size.xy[cross] = inner.xy[cross]; | |
} else { | |
if (box->layout.align == MI_FILL) | |
child->rect.size.xy[cross] = inner.xy[cross]; | |
else if (box->layout.align == MI_END) | |
child->rect.pos.xy[cross] += inner.xy[cross] - child->rect.size.xy[cross]; | |
else if (box->layout.align == MI_CENTER) | |
child->rect.pos.xy[cross] += inner.xy[cross]/2 - child->rect.size.xy[cross]/2; | |
} | |
// Pack main | |
int childGrow = mi__getGrow(child, main); | |
if (avail >= 0 && childGrow > 0 && grow > 0.0f) | |
child->rect.size.xy[main] += (float)childGrow * grow; | |
else | |
child->rect.size.xy[main] *= scale; | |
orig.xy[main] += child->rect.size.xy[main] + box->layout.spacing; | |
mi__layoutBoxes(child); | |
} | |
} | |
void miBoxAddChildrenArgs(MIbox* box, ...) | |
{ | |
// Find tail of the child list | |
MIbox* prev = NULL; | |
for (MIbox* child = box->child; child; child = child->next) | |
prev = child; | |
// Add all children | |
va_list list; | |
va_start(list, box); | |
MIbox* child = va_arg(list, MIbox*); | |
while (child != NULL) { | |
if (prev) | |
prev->next = child; | |
else | |
box->child = child; | |
prev = child; | |
child = va_arg(list, MIbox*); | |
} | |
va_end(list); | |
} | |
void miBoxAddChild(MIbox* box, MIbox* newChild) | |
{ | |
MIbox* prev = NULL; | |
for (MIbox* child = box->child; child; child = child->next) | |
prev = child; | |
if (prev) | |
prev->next = newChild; | |
else | |
box->child = newChild; | |
} | |
static void mi__boxOffset(MIbox* box, MIpoint delta) | |
{ | |
box->rect.x += delta.x; | |
box->rect.y += delta.y; | |
for (MIbox* child = box->child; child; child = child->next) | |
mi__boxOffset(child, delta); | |
} | |
void miBoxMoveTo(MIbox* box, MIpoint pos) | |
{ | |
MIpoint delta = { .x = pos.x - box->rect.x, .y = pos.y - box->rect.y }; | |
box->rect.x = pos.x; | |
box->rect.y = pos.y; | |
for (MIbox* child = box->child; child; child = child->next) | |
mi__boxOffset(child, delta); | |
} | |
void miBoxLayout(MIbox* box) | |
{ | |
miMeasureContent(box); | |
mi__layoutBoxes(box); | |
} | |
void miBoxLayoutToRect(MIbox* box, MIrect rect, MIlayout layout) | |
{ | |
MIbox* savedNext = box->next; | |
box->next = NULL; | |
MIbox container = { | |
.rect = rect, | |
.content.width = mi__maxf(0.0f, rect.width - layout.pad.x*2), | |
.content.height = mi__maxf(0.0f, rect.height - layout.pad.y*2), | |
.layout = layout, | |
.child = box | |
}; | |
miBoxLayout(&container); | |
box->next = savedNext; | |
} | |
void miRectCut(MIrect* rect, MIbox* item, MIlayout layout) | |
{ | |
int main = layout.dir; | |
int cross = layout.dir ^ 1; | |
MIpoint orig = { | |
.x = rect->x + layout.pad.x, | |
.y = rect->y + layout.pad.y | |
}; | |
MIsize inner = { | |
.width = mi__maxf(0, rect->width - layout.pad.x*2), | |
.height = mi__maxf(0, rect->height - layout.pad.y*2) | |
}; | |
// Align | |
if (layout.align == MI_FILL) | |
item->rect.size.xy[cross] = inner.xy[cross]; | |
else if (layout.align == MI_END) | |
orig.xy[cross] += inner.xy[cross] - item->rect.size.xy[cross]; | |
else if (layout.align == MI_CENTER) | |
orig.xy[cross] += inner.xy[cross]/2 - item->rect.size.xy[cross]/2; | |
// Pack | |
float advance = item->rect.size.xy[main] + layout.spacing; | |
if (layout.pack == MI_END) { | |
orig.xy[main] += inner.xy[main] - item->rect.size.xy[main]; | |
rect->pos.xy[main] += mi__minf(0, rect->size.xy[main] - advance); | |
rect->size.xy[main] = mi__maxf(0, rect->size.xy[main] - advance); | |
} else { // Threat everything else as pack start | |
rect->pos.xy[main] += advance; | |
rect->size.xy[main] = mi__maxf(0, rect->size.xy[main] - advance); | |
} | |
miBoxMoveTo(item, orig); | |
} | |
void miRectCutAndLayout(MIrect* rect, MIbox* box, MIlayout layout) | |
{ | |
miBoxLayoutToRect(box, *rect, layout); | |
miRectCut(rect, box, layout); | |
} | |
MIrect miRectInset(MIrect rect, MIsize inset) | |
{ | |
inset.x = mi__minf(inset.x, rect.width*0.5f); | |
inset.y = mi__minf(inset.y, rect.height*0.5f); | |
return (MIrect){ | |
.x = rect.x + inset.x, | |
.y = rect.y + inset.y, | |
.width = rect.width - inset.x*2, | |
.height = rect.height - inset.y*2 | |
}; | |
} | |
MIrect miRectOutset(MIrect rect, MIsize outset) | |
{ | |
return (MIrect){ | |
.x = rect.x - outset.x, | |
.y = rect.y - outset.y, | |
.width = rect.width + outset.x*2, | |
.height = rect.height + outset.y*2 | |
}; | |
} | |
MIrect miRectIntersect(MIrect rectA, MIrect rectB) | |
{ | |
float minx = mi__maxf(rectA.x, rectB.x); | |
float miny = mi__maxf(rectA.y, rectB.y); | |
float maxx = mi__minf(rectA.x + rectA.width, rectB.x + rectB.width); | |
float maxy = mi__minf(rectA.y + rectA.height, rectB.y + rectB.height); | |
return (MIrect){ | |
.x = minx, | |
.y = miny, | |
.width = mi__maxf(0.0f, maxx - minx), | |
.height = mi__maxf(0.0f, maxy - miny) | |
}; | |
} | |
static float mi__align(float size, int align) | |
{ | |
if (align == MI_CENTER) | |
return size * 0.5f; | |
else if (align == MI_END) | |
return size; | |
return 0.0f; | |
} | |
MIpoint miRectCorner(MIrect rect, int alignx, int aligny) | |
{ | |
return (MIpoint) { | |
.x = rect.x + mi__align(rect.width, alignx), | |
.y = rect.y + mi__align(rect.height, aligny) | |
}; | |
} | |
MIpoint miPointAdd(MIpoint a, MIpoint b) | |
{ | |
return (MIpoint) { | |
.x = a.x + b.x, | |
.y = a.y + b.y | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef MILAYOUT_H | |
#define MILAYOUT_H | |
enum MIdirection { | |
MI_ROW = 0, | |
MI_COL, | |
}; | |
enum MIpackAndAlign { | |
MI_FILL = 0, | |
MI_START, | |
MI_CENTER, | |
MI_END, | |
}; | |
enum MIoverflow { | |
MI_FIT = 0, | |
MI_SCROLL, | |
}; | |
struct MIpoint { | |
union { | |
struct { float x, y; }; | |
struct { float xy[2]; }; | |
}; | |
}; | |
typedef struct MIpoint MIpoint; | |
struct MIsize { | |
union { | |
struct { float x, y; }; | |
struct { float width, height; }; | |
struct { float xy[2]; }; | |
}; | |
}; | |
typedef struct MIsize MIsize; | |
struct MIrect { | |
union { | |
struct { float x, y, width, height; }; | |
struct { MIpoint pos; MIsize size; }; | |
}; | |
}; | |
typedef struct MIrect MIrect; | |
struct MIlayout { | |
MIsize pad; | |
float spacing; | |
unsigned char dir, align, pack, overflow; | |
}; | |
typedef struct MIlayout MIlayout; | |
struct MIbox { | |
MIsize content; | |
unsigned char grow; | |
MIlayout layout; | |
MIrect rect; | |
struct MIbox* child; | |
struct MIbox* next; | |
}; | |
typedef struct MIbox MIbox; | |
#define miBoxAddChildren(box, ...) miBoxAddChildrenArgs(box, __VA_ARGS__, NULL) | |
void miBoxAddChildrenArgs(MIbox* box, ...); | |
void miBoxAddChild(MIbox* box, MIbox* newChild); | |
void miBoxMoveTo(MIbox* box, MIpoint pos); | |
void miMeasureContent(MIbox* box); | |
void miBoxLayout(MIbox* box); | |
void miBoxLayoutToRect(MIbox* box, MIrect rect, MIlayout layout); | |
void miRectCut(MIrect* rect, MIbox* box, MIlayout layout); | |
void miRectCutAndLayout(MIrect* rect, MIbox* box, MIlayout layout); | |
MIrect miRectInset(MIrect rect, MIsize inset); | |
MIrect miRectOutset(MIrect rect, MIsize outset); | |
MIrect miRectIntersect(MIrect rectA, MIrect rectB); | |
MIpoint miRectCorner(MIrect rect, int alignx, int aligny); | |
MIpoint miPointAdd(MIpoint a, MIpoint b); | |
#endif // MILAYOUT_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Menu Layout | |
MIitem item1 = { .text="Item 1", .detail="Alt+Shift+Space" }; | |
MIitem item2 = { .flags=MI_ITEM_CHECKED, .icon=ICON_EMOJI_PEOPLE, .text="People", .detail="Alt+P" }; | |
MIitem item3 = { .flags=MI_ITEM_SUBMENU, .icon=ICON_EMAIL, .text="Email" }; | |
MIbox itemSearchCont = { .layout.dir = MI_ROW, .layout.spacing = 4, }; | |
MIbox itemSearchIcon = { .content = miMeasureIcon() }; | |
MIbox itemSearchInput = {}; | |
miBoxAddChildren(&itemSearchCont, &itemSearchIcon, &itemSearchInput); | |
MIbox itemBox1 = { .content = miMeasureItem(item1) }; | |
MIbox itemBox2 = { .content = miMeasureItem(item2) }; | |
MIbox itemBox3 = { .content = miMeasureItem(item3) }; | |
MIbox menuCont = { | |
.rect.pos = miRectCorner(button3.rect, MI_START, MI_END), | |
.layout.dir = MI_COL, .layout.pack = MI_START, | |
.layout.spacing = 4, .layout.pad.x = 6, .layout.pad.y = 6, | |
}; | |
miBoxAddChildren(&menuCont, &itemSearchCont, &itemBox1, &itemBox2, &itemBox3); | |
miBoxLayout(&menuCont); | |
// Menu logic | |
MIhandle panelHandle = miPanelBegin(MI_PANEL_POPUP, menuCont.rect); | |
miIcon(itemSearchIcon.rect, ICON_SEARCH); | |
MIhandle menuInput = miInput(itemSearchInput.rect, (MIinput){.text=text, .maxText=sizeof(text)}); | |
if (miBlurred(menuInput)) | |
miPopupHide(menu); | |
miItem(itemBox1.rect, item1); | |
miItem(itemBox2.rect, item2); | |
MIhandle item3Handle = miItem(itemBox3.rect, item3); | |
miPanelEnd(panelHandle); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment