Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+805
View File
@@ -0,0 +1,805 @@
import elements.BaseGuiElement;
import elements.GuiScrollbar;
import elements.GuiMarkupText;
export GuiListElement, GuiListText, GuiListbox;
export GuiMarkupListText;
class GuiListElement {
void set(const string& txt) {
throw("Setting item text on a non-textual list element.");
}
string get() {
return "";
}
string get_tooltipText() {
return "";
}
ITooltip@ get_tooltip() {
return null;
}
bool get_isSelectable() {
return true;
}
bool onMouseEvent(const MouseEvent& event) {
return false;
}
void onSelect() {
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
}
int opCmp(const GuiListElement@ other) const {
return 0;
}
};
class GuiListText : GuiListElement {
string text;
Sprite icon;
GuiListText(const string& txt, const Sprite& icon = Sprite()) {
text = txt;
this.icon = icon;
}
void set(const string& txt) {
text = txt;
}
string get() {
return text;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
const Font@ font = ele.skin.getFont(ele.TextFont);
int baseLine = font.getBaseline();
vec2i textOffset(ele.horizPadding, (absPos.size.height - baseLine) / 2);
if(ele.itemStyle == SS_NULL)
ele.skin.draw(SS_ListboxItem, flags, absPos);
if(icon.valid) {
vec2i iconSize = icon.size;
if(iconSize.y > absPos.height) {
iconSize.x = double(absPos.height) / double(iconSize.y) * double(iconSize.x);
iconSize.y = absPos.height;
}
vec2i iconOffset(ele.horizPadding, (absPos.height - iconSize.height) / 2);
icon.draw(recti_area(absPos.topLeft + iconOffset, iconSize));
textOffset.x += iconSize.width + 8;
}
font.draw(absPos.topLeft + textOffset, text);
}
};
class GuiMarkupListText : GuiListElement {
string text;
string basicText;
MarkupRenderer renderer;
GuiMarkupListText(const string& txt, FontType font = FT_Normal) {
text = txt;
renderer.parseTree(txt);
renderer.defaultFont = font;
}
void set(const string& txt) {
text = txt;
renderer.parseTree(txt);
}
string get() {
return basicText.length == 0 ? text : basicText;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
if(ele.itemStyle == SS_NULL)
ele.skin.draw(SS_ListboxItem, flags, absPos);
renderer.draw(ele.skin, absPos.padded(ele.horizPadding, ele.vertPadding));
}
};
class GuiListbox : BaseGuiElement {
FontType TextFont = FT_Normal;
GuiListElement@[] items;
int selected = -1;
int hovered = -1;
int lineHeight = 0;
int horizPadding = 6;
int vertPadding = 5;
bool Required = false;
bool hoverSelect = false;
bool ElementHovered = false;
bool ElementFocused = false;
bool Multiple = false;
bool autoMultiple = true;
bool[] SelectedItems;
bool DblClickConfirm = true;
int SelectedCount = 0;
int PrevSelected = -1;
int itemHeight = -1;
bool disabled = false;
string search;
double searchTime = 0;
double doubleTime = 0;
SkinStyle style = SS_Listbox;
SkinStyle itemStyle = SS_NULL;
GuiScrollbar@ scroll;
GuiListbox(IGuiElement@ ParentElement, const recti& Rectangle) {
_GuiListbox();
super(ParentElement, Rectangle);
@scroll.parent = this;
}
GuiListbox(IGuiElement@ ParentElement, Alignment@ Align) {
_GuiListbox();
super(ParentElement, Align);
@scroll.parent = this;
}
void sortAsc() {
items.sortAsc();
}
void sortDesc() {
items.sortDesc();
}
void scrollToEnd() {
scroll.pos = max(0.0, scroll.end - scroll.page);
}
void _GuiListbox() {
@scroll = GuiScrollbar(null, recti());
scroll.alignment.set(
AS_Right, 0.0, 20, AS_Top, 0.0, 0,
AS_Right, 0.0, 0, AS_Bottom, 0.0, 0);
}
void addItem(GuiListElement@ elem, uint where) {
items.insertAt(where, elem);
if(multiple) {
if(Required && SelectedCount <= 0) {
SelectedItems.insertLast(true);
SelectedCount = 1;
}
else
SelectedItems.insertLast(false);
}
else if(Required && selected == -1) {
selected = 0;
}
updateAbsolutePosition();
}
void addItem(GuiListElement@ elem) {
addItem(elem, items.length);
}
void addItem(const string& item, uint where) {
addItem(GuiListText(item), where);
}
void addItem(const string& item) {
addItem(GuiListText(item), items.length);
}
void removeItem(uint index) {
if(index < items.length()) {
items.removeAt(index);
if(multiple) {
if(SelectedItems[index])
SelectedCount -= 1;
SelectedItems.removeAt(index);
}
else {
if(selected >= int(items.length()))
selected = items.length() - 1;
}
}
updateAbsolutePosition();
}
void removeItemsFrom(uint index) {
if(items.length > index)
items.length = index;
if(selected > 0 && selected >= int(index))
selected = index - 1;
updateAbsolutePosition();
}
void clearItems() {
SelectedItems.length = 0;
items.length = 0;
selected = -1;
hovered = -1;
if(Tooltip !is null)
Tooltip.update(skin, this);
updateAbsolutePosition();
}
void setItem(uint index, const string& item) {
if(index < items.length())
items[index].set(item);
else
addItem(item);
}
void setItem(uint index, GuiListElement@ elem) {
if(index < items.length())
@items[index] = elem;
else
addItem(elem);
}
string getItem(uint index) {
if(index < items.length())
return items[index].get();
return "";
}
GuiListElement@ getItemElement(uint index) {
if(index < items.length())
return items[index];
return null;
}
GuiListElement@ get_selectedItem() {
if(selected == -1 || Multiple)
return null;
return items[selected];
}
GuiListElement@ get_hoveredItem() {
if(hovered == -1)
return null;
return items[hovered];
}
uint get_itemCount() {
return items.length();
}
bool isSelected(uint index) {
if(index >= items.length())
return false;
if(multiple)
return SelectedItems[index];
else
return int(index) == selected;
}
uint get_selectedCount() {
if(multiple)
return SelectedCount;
else
return selected == -1 ? 0 : 1;
}
void clearSelection() {
if(multiple) {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(Required && SelectedItems.length() > 0) {
SelectedCount = 1;
SelectedItems[0] = true;
}
else {
SelectedCount = 0;
}
}
else {
selected = -1;
}
}
ITooltip@ get_tooltipObject() {
if(hovered >= 0 && hovered < int(items.length)) {
ITooltip@ tt = items[hovered].tooltip;
if(tt !is null)
return tt;
}
return Tooltip;
}
string get_tooltip() {
if(hovered != -1 && uint(hovered) < items.length) {
string tt = items[hovered].tooltipText;
if(tt.length != 0)
return tt;
}
return BaseGuiElement::get_tooltip();
}
void set_multiple(bool mult) {
if(Multiple == mult)
return;
Multiple = mult;
if(Multiple) {
SelectedItems.resize(items.length());
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(selected != -1) {
SelectedItems[selected] = true;
SelectedCount = 1;
}
else {
if(Required && SelectedItems.length() > 0) {
SelectedItems[0] = true;
SelectedCount = 1;
}
else {
SelectedCount = 0;
}
}
}
else {
SelectedItems.resize(0);
if(Required && items.length() > 0)
selected = 0;
else
selected = -1;
}
}
bool get_multiple() {
return Multiple;
}
void set_font(FontType type) {
if(TextFont == type)
return;
TextFont = type;
updateAbsolutePosition();
}
FontType get_font() const {
return TextFont;
}
void set_required(bool req) {
Required = req;
if(Required) {
if(Multiple) {
if(SelectedCount <= 0 && items.length() > 0) {
SelectedCount = 1;
SelectedItems[0] = true;
}
}
else {
if(selected == -1 && items.length() > 0)
selected = 0;
}
}
}
bool get_required() const {
return Required;
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Entered:
ElementHovered = true;
break;
case GUI_Mouse_Left:
ElementHovered = false;
break;
case GUI_Focused:
ElementFocused = true;
break;
case GUI_Focus_Lost:
ElementFocused = false;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
void updateHover() {
vec2i offset = mousePos - AbsolutePosition.topLeft;
int prevHovered = hovered;
hovered = getOffsetItem(offset);
if(prevHovered != hovered)
emitHoverChanged(hovered);
if(hovered != prevHovered && Tooltip !is null)
Tooltip.update(skin, this);
if(hoverSelect && hovered >= 0 && !disabled)
selected = hovered;
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
updateHover();
if(hovered >= 0 && items[hovered].onMouseEvent(event))
return true;
switch(event.type) {
case MET_Button_Down:
if(event.button == 0)
return true;
break;
case MET_Button_Up: {
if(disabled)
return true;
if(hoverSelect || !ElementFocused || event.button != 0)
break;
bool wasConfirmation = false;
if(hovered == -1 || !items[hovered].isSelectable) {
if(!Required)
clearSelection();
}
else if(Multiple) {
if(shiftKey && PrevSelected != -1) {
int from = PrevSelected <= hovered ? PrevSelected : hovered;
int to = PrevSelected > hovered ? PrevSelected : hovered;
bool doSelected = SelectedItems[PrevSelected];
for(int i = from; i <= to; ++i) {
if(doSelected) {
if(!SelectedItems[i]) {
SelectedItems[i] = true;
SelectedCount += 1;
}
}
else {
if(SelectedItems[i] && (!Required || SelectedCount > 1)) {
SelectedItems[i] = false;
SelectedCount -= 1;
}
}
}
}
else {
PrevSelected = hovered;
if(SelectedItems[hovered]) {
if(!ctrlKey && DblClickConfirm && frameTime < doubleTime + double(settings::iDoubleClickMS) * 0.001) {
wasConfirmation = true;
emitConfirmed(hovered);
doubleTime = -INFINITY;
}
else if(ctrlKey || autoMultiple) {
if(!Required || SelectedCount > 1) {
SelectedItems[hovered] = false;
SelectedCount -= 1;
}
}
else {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(Required || SelectedCount > 1) {
SelectedItems[hovered] = true;
SelectedCount = 1;
}
else {
SelectedCount = 0;
}
}
}
else {
if(ctrlKey || autoMultiple) {
SelectedItems[hovered] = true;
SelectedCount += 1;
doubleTime = frameTime;
}
else {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
SelectedItems[hovered] = true;
SelectedCount = 1;
doubleTime = frameTime;
}
}
}
}
else {
if(selected == hovered && DblClickConfirm && frameTime < doubleTime + double(settings::iDoubleClickMS) * 0.001) {
wasConfirmation = true;
emitConfirmed(hovered);
doubleTime = -INFINITY;
}
else if(selected == hovered && !Required)
selected = -1;
else if(!Required || hovered != -1)
selected = hovered;
}
if(!wasConfirmation)
doubleTime = frameTime;
if(selected >= 0 && !wasConfirmation)
items[selected].onSelect();
emitChange();
} return true;
case MET_Scrolled:
if(scroll.visible)
return scroll.onMouseEvent(event, scroll);
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
void nextItem() {
if(selected >= 0 && selected < int(items.length()) - 1) {
selected += 1;
if((selected + 1) * lineHeight - scroll.pos >= AbsolutePosition.height)
scroll.pos = min(scroll.end - scroll.page, scroll.pos + lineHeight);
}
}
void prevItem() {
if(selected > 0) {
selected -= 1;
if(selected * lineHeight - scroll.pos < 0)
scroll.pos = max(0.0, scroll.pos - lineHeight);
}
}
void updateScroll() {
if(selected * lineHeight - scroll.pos < 0)
scroll.pos = max(0.0, double(selected * lineHeight));
else if((selected + 1) * lineHeight - scroll.pos >= AbsolutePosition.height)
scroll.pos = min(scroll.end - scroll.page, double(selected * lineHeight));
}
void doSearch() {
searchTime = frameTime;
uint start = selected == -1 ? 0 : uint(selected);
int found = -1;
for(uint i = 0, cnt = items.length; i < cnt; ++i) {
int index = int((start + i) % cnt);
string text = items[index].get();
if(text.length == 0)
continue;
if(text.startswith_nocase(search)) {
found = index;
break;
}
}
if(found != -1 && selected != found) {
selected = found;
emitChange();
updateScroll();
}
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(source is this) {
if(!Multiple) {
if(event.type == KET_Key_Down) {
int pageItems = ceil(double(AbsolutePosition.height) / double(lineHeight));
switch(event.key) {
case KEY_UP:
if(!disabled)
prevItem();
return true;
case KEY_DOWN:
if(!disabled)
nextItem();
return true;
case KEY_ENTER:
return true;
case KEY_PAGEUP:
if(selected > 0) {
if(disabled)
return true;
selected = max(0, selected - pageItems);
scroll.pos = max(0.0, scroll.pos - AbsolutePosition.height);
return true;
}
break;
case KEY_PAGEDOWN:
if(selected >= 0) {
if(disabled)
return true;
selected = min(items.length() - 1, selected + pageItems);
scroll.pos = min(scroll.end - scroll.page, scroll.pos + AbsolutePosition.height);
return true;
}
break;
case KEY_BACKSPACE: {
if(disabled)
return true;
int len = search.length;
int curs = len;
int prevPos = len;
int tmp = 0;
if(prevPos == 0)
return true;
u8prev(search, prevPos, tmp);
string newText;
if(prevPos > 0)
newText += search.substr(0, prevPos);
if(len - curs > 0)
newText += search.substr(curs, len - curs);
search = newText;
doSearch();
return true;
}
}
}
else if(event.type == KET_Key_Up) {
switch(event.key) {
case KEY_UP:
return true;
case KEY_DOWN:
return true;
case KEY_ENTER:
emitChange();
emitConfirmed();
return true;
case KEY_PAGEUP:
if(selected > 0)
return true;
break;
case KEY_PAGEDOWN:
if(selected >= 0)
return true;
break;
case KEY_BACKSPACE:
return true;
}
}
else if(event.type == KET_Key_Typed) {
if(disabled)
return true;
if(searchTime < frameTime - 0.5)
search = "";
u8append(search, event.key);
doSearch();
return true;
}
}
return scroll.onKeyEvent(event, scroll);
}
return BaseGuiElement::onKeyEvent(event, source);
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
if(skin is null)
return;
const Font@ font = skin.getFont(TextFont);
if(itemHeight == -1)
lineHeight = font.getLineHeight() + vertPadding * 2;
else
lineHeight = itemHeight;
scroll.scroll = lineHeight * 2;
scroll.page = AbsolutePosition.height;
scroll.bar = scroll.page;
scroll.end = lineHeight * items.length();
scroll.pos = max(0.0, min(scroll.end - scroll.page, scroll.pos));
scroll.visible = scroll.end > scroll.page;
scroll.updateAbsolutePosition();
}
vec2i get_desiredSize() const {
return vec2i(size.width, lineHeight * items.length);
}
int getOffsetItem(const vec2i& offset) {
if(offset.x < 0 || offset.x > AbsolutePosition.width)
return -1;
int item = floor(double(offset.y + scroll.pos) / double(lineHeight));
if(item < 0 || item >= int(items.length()))
return -1;
else
return item;
}
vec2i getItemOffset(int item) {
if(item < 0 || item >= int(items.length))
return vec2i();
return vec2i(0, (item * lineHeight) - int(scroll.pos));
}
void draw() {
//Draw element background
uint flags = SF_Normal;
if(ElementHovered)
flags |= SF_Hovered;
if(ElementFocused)
flags |= SF_Focused;
if(disabled)
flags |= SF_Disabled;
skin.draw(style, SF_Normal, AbsoluteClipRect);
//Draw items
int itemWidth = AbsolutePosition.width;
if(scroll.visible)
itemWidth -= 20;
recti itemPos = recti_area(
AbsolutePosition.topLeft - vec2i(0, scroll.pos),
vec2i(itemWidth, lineHeight));
for(uint i = 0, cnt = items.length(); i < cnt; ++i) {
//Don't display completely invisible items
if(itemPos.botRight.y < AbsolutePosition.topLeft.y) {
itemPos += vec2i(0, lineHeight);
continue;
}
if(itemPos.topLeft.y > AbsolutePosition.botRight.y)
break;
//Figure correct style to use
uint itFlags = SF_Normal;
if(items[i].isSelectable) {
if(int(i) == hovered && !disabled) {
if(ElementHovered)
itFlags |= SF_Hovered;
if(ElementFocused)
itFlags |= SF_Focused;
}
if(Multiple) {
if(SelectedItems[i])
itFlags |= SF_Active;
}
else if(int(i) == selected) {
itFlags |= SF_Active;
}
}
//Draw item
if(itemStyle != SS_NULL)
skin.draw(itemStyle, itFlags, itemPos);
items[i].draw(this, itFlags, itemPos);
itemPos += vec2i(0, lineHeight);
}
BaseGuiElement::draw();
}
};