1777 lines
44 KiB
ActionScript

/*
Copyright (c) 2013 yvt
This file is part of OpenSpades.
OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
*/
namespace spades {
namespace ui {
class Label: UIElement {
string Text;
Vector4 BackgroundColor = Vector4(0, 0, 0, 0);
Vector4 TextColor = Vector4(1, 1, 1, 1);
Vector2 Alignment = Vector2(0.f, 0.0f);
float TextScale = 1.f;
Label(UIManager@ manager) {
super(manager);
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
if(BackgroundColor.w > 0.f) {
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.ColorNP = BackgroundColor;
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
}
if(Text.length > 0) {
Font@ font = this.Font;
string text = this.Text;
Vector2 txtSize = font.Measure(text) * TextScale;
Vector2 txtPos;
txtPos = pos + (size - txtSize) * Alignment;
font.Draw(text, txtPos, TextScale, TextColor);
}
}
}
class ButtonBase: UIElement {
bool Pressed = false;
bool Hover = false;
bool Toggled = false;
bool Toggle = false;
bool Repeat = false;
bool ActivateOnMouseDown = false;
EventHandler@ Activated;
EventHandler@ DoubleClicked;
EventHandler@ RightClicked;
string Caption;
string ActivateHotKey;
private Timer@ repeatTimer;
// for double click detection
private float lastActivate = -1.f;
private Vector2 lastActivatePosition = Vector2();
ButtonBase(UIManager@ manager) {
super(manager);
IsMouseInteractive = true;
@repeatTimer = Timer(Manager);
@repeatTimer.Tick = TimerTickEventHandler(this.RepeatTimerFired);
}
void PlayMouseEnterSound() {
Manager.PlaySound("Sounds/Feedback/Limbo/Hover.opus");
}
void PlayActivateSound() {
Manager.PlaySound("Sounds/Feedback/Limbo/Select.opus");
}
void OnActivated() {
if(Activated !is null) {
Activated(this);
}
}
void OnDoubleClicked() {
if(DoubleClicked !is null) {
DoubleClicked(this);
}
}
void OnRightClicked() {
if(RightClicked !is null) {
RightClicked(this);
}
}
private void RepeatTimerFired(Timer@ timer) {
OnActivated();
timer.Interval = 0.1f;
}
void MouseDown(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton &&
button != spades::ui::MouseButton::RightMouseButton) {
return;
}
PlayActivateSound();
if (button == spades::ui::MouseButton::RightMouseButton) {
OnRightClicked();
return;
}
Pressed = true;
Hover = true;
if(Repeat or ActivateOnMouseDown) {
OnActivated();
if(Repeat) {
repeatTimer.Interval = 0.3f;
repeatTimer.Start();
}
}
}
void MouseMove(Vector2 clientPosition) {
if(Pressed) {
bool newHover = AABB2(Vector2(0.f, 0.f), Size).Contains(clientPosition);
if(newHover != Hover) {
if(Repeat) {
if(newHover) {
OnActivated();
repeatTimer.Interval = 0.3f;
repeatTimer.Start();
} else {
repeatTimer.Stop();
}
}
Hover = newHover;
}
}
}
void MouseUp(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton &&
button != spades::ui::MouseButton::RightMouseButton) {
return;
}
if(Pressed) {
Pressed = false;
if(Hover and not (Repeat or ActivateOnMouseDown)) {
if(Toggle) {
Toggled = not Toggled;
}
OnActivated();
if (Manager.Time < lastActivate + 0.35 &&
(clientPosition - lastActivatePosition).ManhattanLength < 10.0f) {
OnDoubleClicked();
}
lastActivate = Manager.Time;
lastActivatePosition = clientPosition;
}
if(Repeat and Hover){
repeatTimer.Stop();
}
}
}
void MouseEnter() {
Hover = true;
if(not Pressed) {
PlayMouseEnterSound();
}
UIElement::MouseEnter();
}
void MouseLeave() {
Hover = false;
UIElement::MouseLeave();
}
void KeyDown(string key) {
if(key == " ") {
OnActivated();
}
UIElement::KeyDown(key);
}
void KeyUp(string key) {
UIElement::KeyUp(key);
}
void HotKey(string key) {
if(key == ActivateHotKey) {
OnActivated();
}
}
}
class SimpleButton: spades::ui::Button {
SimpleButton(spades::ui::UIManager@ manager){
super(manager);
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if((Pressed && Hover) || Toggled) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.12f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.07f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
if((Pressed && Hover) || Toggled) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.1f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.07f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.03f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, 1.f, size.y));
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x+size.x-1.f, pos.y, 1.f, size.y));
renderer.DrawImage(img, AABB2(pos.x, pos.y+size.y-1.f, size.x, 1.f));
Vector2 txtSize = Font.Measure(Caption);
float margin = 4.f;
Font.DrawShadow(Caption, pos + Vector2(margin, margin) +
(size - txtSize - Vector2(margin * 2.f, margin * 2.f)) * Alignment,
1.f, Vector4(1,1,1,1), Vector4(0,0,0,0.4f));
}
}
class CheckBox: spades::ui::Button {
CheckBox(spades::ui::UIManager@ manager){
super(manager);
this.Toggle = true;
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if(Pressed && Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.12f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.00f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
Vector2 txtSize = Font.Measure(Caption);
Font.DrawShadow(Caption, pos + (size - txtSize) * Vector2(0.f, 0.5f) + Vector2(16.f, 0.f),
1.f, Vector4(1,1,1,1), Vector4(0,0,0,0.2f));
@img = renderer.RegisterImage("Gfx/UI/CheckBox.png");
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, Toggled ? .9f : 0.6f);
renderer.DrawImage(img, AABB2(pos.x, pos.y + (size.y - 16.f) * 0.5f, 16.f, 16.f),
AABB2(Toggled ? 16.f : 0.f, 0.f, 16.f, 16.f));
}
}
class RadioButton: spades::ui::Button {
string GroupName;
RadioButton(spades::ui::UIManager@ manager){
super(manager);
this.Toggle = true;
}
void Check() {
this.Toggled = true;
// uncheck others
if(GroupName.length > 0) {
UIElement@[]@ children = this.Parent.GetChildren();
for(uint i = 0, count = children.length; i < children.length; i++) {
RadioButton@ btn = cast<RadioButton>(children[i]);
if(btn is this) continue;
if(btn !is null) {
if(GroupName == btn.GroupName) {
btn.Toggled = false;
}
}
}
}
}
void OnActivated() {
Check();
Button::OnActivated();
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if(!this.Enable) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.07f);
} else if((Pressed && Hover) || Toggled) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.12f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.07f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
if(!this.Enable) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.03f);
} if((Pressed && Hover) || Toggled) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.1f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.07f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.03f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, 1.f, size.y));
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x+size.x-1.f, pos.y, 1.f, size.y));
renderer.DrawImage(img, AABB2(pos.x, pos.y+size.y-1.f, size.x, 1.f));
Vector2 txtSize = Font.Measure(Caption);
Font.DrawShadow(Caption, pos + (size - txtSize) * 0.5f + Vector2(8.f, 0.f), 1.f,
Vector4(1,1,1,this.Enable ? 1.f : 0.4f), Vector4(0,0,0,0.4f));
if(Toggled) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.6f);
renderer.DrawImage(img, AABB2(pos.x + 4.f, pos.y + (size.y - 8.f) * 0.5f, 8.f, 8.f));
}
}
}
class Button: ButtonBase {
private Image@ image;
Vector2 Alignment = Vector2(0.5f, 0.5f);
Button(UIManager@ manager) {
super(manager);
Renderer@ renderer = Manager.Renderer;
@image = renderer.RegisterImage("Gfx/UI/Button.png");
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Vector4 color = Vector4(0.2f, 0.2f, 0.2f, 0.5f);
if(Toggled or (Pressed and Hover)) {
color = Vector4(0.7f, 0.7f, 0.7f, 0.9f);
}else if(Hover) {
color = Vector4(0.4f, 0.4f, 0.4f, 0.7f);
}
if(!IsEnabled) {
color.w *= 0.5f;
}
renderer.ColorNP = color;
DrawSliceImage(renderer, image, pos.x, pos.y, size.x, size.y, 12.f);
Font@ font = this.Font;
string text = this.Caption;
Vector2 txtSize = font.Measure(text);
Vector2 txtPos;
pos += Vector2(8.f, 8.f);
size -= Vector2(16.f, 16.f);
txtPos = pos + (size - txtSize) * Alignment;
if(IsEnabled){
font.DrawShadow(text, txtPos, 1.f,
Vector4(1.f, 1.f, 1.f, 1.f), Vector4(0.f, 0.f, 0.f, 0.4f));
}else{
font.DrawShadow(text, txtPos, 1.f,
Vector4(1.f, 1.f, 1.f, 0.5f), Vector4(0.f, 0.f, 0.f, 0.1f));
}
}
}
class FieldCommand {
int index;
string oldString;
string newString;
}
class FieldBase: UIElement {
bool Dragging = false;
EventHandler@ Changed;
string Placeholder;
int MarkPosition = 0;
int CursorPosition = 0;
int MaxLength = 255;
bool DenyNonAscii = false;
private string text;
private FieldCommand@[] history;
private int historyPos = 0; // index to insert next command
Vector4 TextColor = Vector4(1.f, 1.f, 1.f, 1.f);
Vector4 DisabledTextColor = Vector4(1.f, 1.f, 1.f, 0.3f);
Vector4 PlaceholderColor = Vector4(1.f, 1.f, 1.f, 0.5f);
Vector4 HighlightColor = Vector4(1.f, 1.f, 1.f, 0.3f);
Vector2 TextOrigin = Vector2(0.f, 0.f);
float TextScale = 1.f;
FieldBase(UIManager@ manager) {
super(manager);
IsMouseInteractive = true;
AcceptsFocus = true;
@this.Cursor = Cursor(Manager, manager.Renderer.RegisterImage("Gfx/UI/IBeam.png"), Vector2(16.f, 16.f));
}
string Text {
get { return text; }
set {
text = value;
EraseUndoHistory();
}
}
private void EraseUndoHistory() {
history.length = 0;
historyPos = 0;
}
private bool CheckCharType(string s) {
if(DenyNonAscii) {
for(uint i = 0, len = s.length; i < len; i++) {
int c = s[i];
if((c & 0x80) != 0) {
return false;
}
}
}
return true;
}
void OnChanged() {
if(Changed !is null) {
Changed(this);
}
}
int SelectionStart {
get final { return Min(MarkPosition, CursorPosition); }
set {
Select(value, SelectionEnd - value);
}
}
int SelectionEnd {
get final {
return Max(MarkPosition, CursorPosition);
}
set {
Select(SelectionStart, value - SelectionStart);
}
}
int SelectionLength {
get final {
return SelectionEnd - SelectionStart;
}
set {
Select(SelectionStart, value);
}
}
string SelectedText {
get final {
return Text.substr(SelectionStart, SelectionLength);
}
set {
if(!CheckCharType(value)) {
return;
}
FieldCommand cmd;
cmd.oldString = this.SelectedText;
if(cmd.oldString == value) return; // no change
cmd.newString = value;
cmd.index = this.SelectionStart;
RunFieldCommand(cmd, true, true);
}
}
private void RunFieldCommand(FieldCommand@ cmd, bool autoSelect, bool addHistory) {
text = text.substr(0, cmd.index) + cmd.newString +
text.substr(cmd.index + cmd.oldString.length);
if(autoSelect)
Select(cmd.index, cmd.newString.length);
if(addHistory) {
history.length = historyPos;
history.insertLast(cmd);
historyPos += 1;
// limit history length
}
}
private void UndoFieldCommand(FieldCommand@ cmd, bool autoSelect) {
text = text.substr(0, cmd.index) + cmd.oldString +
text.substr(cmd.index + cmd.newString.length);
if(autoSelect)
Select(cmd.index, cmd.oldString.length);
}
private void SetHistoryPos(int index) {
int p = historyPos;
FieldCommand@[]@ h = history;
while(p < index) {
RunFieldCommand(h[p], true, false);
p++;
}
while(p > index) {
p--;
UndoFieldCommand(h[p], true);
}
historyPos = p;
}
bool Undo() {
if(historyPos == 0) return false;
SetHistoryPos(historyPos - 1);
return true;
}
bool Redo() {
if(historyPos >= int(history.length)) return false;
SetHistoryPos(historyPos + 1);
return true;
}
AABB2 TextInputRect {
get {
Vector2 textPos = TextOrigin;
Vector2 siz = this.Size;
string text = Text;
int cursorPos = CursorPosition;
Font@ font = this.Font;
float width = font.Measure(text.substr(0, cursorPos)).x;
float fontHeight = font.Measure("A").y;
return AABB2(textPos.x + width, textPos.y,
siz.x - textPos.x - width, fontHeight);
}
}
private int PointToCharIndex(float x) {
x -= TextOrigin.x;
if(x < 0.f) return 0;
x /= TextScale;
string text = Text;
int len = text.length;
float lastWidth = 0.f;
Font@ font = this.Font;
// FIXME: use binary search for better performance?
int idx = 0;
for(int i = 1; i <= len; i++) {
int lastIdx = idx;
idx = GetByteIndexForString(text, 1, idx);
float width = font.Measure(text.substr(0, idx)).x;
if(width > x) {
if(x < (lastWidth + width) * 0.5f) {
return lastIdx;
} else {
return idx;
}
}
lastWidth = width;
if(idx >= len) {
return len;
}
}
return len;
}
int PointToCharIndex(Vector2 pt) {
return PointToCharIndex(pt.x);
}
int ClampCursorPosition(int pos) {
return Clamp(pos, 0, Text.length);
}
void Select(int start, int length = 0) {
MarkPosition = ClampCursorPosition(start);
CursorPosition = ClampCursorPosition(start + length);
}
void SelectAll() {
Select(0, Text.length);
}
void BackSpace() {
if(SelectionLength > 0) {
SelectedText = "";
} else {
int pos = CursorPosition;
int cIdx = GetCharIndexForString(Text, CursorPosition);
int bIdx = GetByteIndexForString(Text, cIdx - 1);
Select(bIdx, pos - bIdx);
SelectedText = "";
}
OnChanged();
}
void Delete() {
if(SelectionLength > 0) {
SelectedText = "";
} else if(CursorPosition < int(Text.length)) {
int pos = CursorPosition;
int cIdx = GetCharIndexForString(Text, CursorPosition);
int bIdx = GetByteIndexForString(Text, cIdx + 1);
Select(bIdx, pos - bIdx);
SelectedText = "";
}
OnChanged();
}
void Insert(string text) {
if(!CheckCharType(text)) {
return;
}
string oldText = SelectedText;
SelectedText = text;
// if text overflows, deny the insertion
if((not FitsInBox(Text)) or (int(Text.length) > MaxLength)) {
SelectedText = oldText;
return;
}
Select(SelectionEnd);
OnChanged();
}
void KeyDown(string key) {
if(key == "BackSpace") {
BackSpace();
}else if(key == "Delete") {
Delete();
}else if(key == "Left") {
if(Manager.IsShiftPressed) {
int cIdx = GetCharIndexForString(Text, CursorPosition);
CursorPosition = ClampCursorPosition(GetByteIndexForString(Text, cIdx - 1));
}else {
if(SelectionLength == 0) {
int cIdx = GetCharIndexForString(Text, CursorPosition);
Select(GetByteIndexForString(Text, cIdx - 1));
} else {
Select(SelectionStart);
}
}
return;
}else if(key == "Right") {
if(Manager.IsShiftPressed) {
int cIdx = GetCharIndexForString(Text, CursorPosition);
CursorPosition = ClampCursorPosition(GetByteIndexForString(Text, cIdx + 1));
}else {
if(SelectionLength == 0) {
int cIdx = GetCharIndexForString(Text, CursorPosition);
Select(GetByteIndexForString(Text, cIdx + 1));
} else {
Select(SelectionEnd);
}
}
return;
}
if(Manager.IsControlPressed or
Manager.IsMetaPressed /* for OSX; Cmd + [a-z] */) {
if(key == "A") {
SelectAll();
return;
}else if(key == "V") {
Manager.Paste(PasteClipboardEventHandler(this.Insert));
}else if(key == "C") {
Manager.Copy(this.SelectedText);
}else if(key == "X") {
Manager.Copy(this.SelectedText);
this.SelectedText = "";
OnChanged();
}else if(key == "Z") {
if(Manager.IsShiftPressed){
if(Redo()) OnChanged();
}else{
if(Undo()) OnChanged();
}
}else if(key == "W") {
if(Redo()) {
OnChanged();
}
}
}
Manager.ProcessHotKey(key);
}
void KeyUp(string key) {
}
void KeyPress(string text) {
if(!(Manager.IsControlPressed or
Manager.IsMetaPressed)) {
Insert(text);
}
}
void MouseDown(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
Dragging = true;
if(Manager.IsShiftPressed) {
MouseMove(clientPosition);
} else {
Select(PointToCharIndex(clientPosition));
}
}
void MouseMove(Vector2 clientPosition) {
if(Dragging) {
CursorPosition = PointToCharIndex(clientPosition);
}
}
void MouseUp(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
Dragging = false;
}
bool FitsInBox(string text) {
return Font.Measure(text).x * TextScale < Size.x - TextOrigin.x;
}
void DrawHighlight(float x, float y, float w, float h) {
Renderer@ renderer = Manager.Renderer;
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.DrawImage(img, AABB2(x, y, w, h));
}
void DrawBeam(float x, float y, float h) {
Renderer@ renderer = Manager.Renderer;
float pulse = sin(Manager.Time * 5.f);
pulse = abs(pulse);
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, pulse);
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.DrawImage(img, AABB2(x - 1.f, y, 2, h));
}
void DrawEditingLine(float x, float y, float w, float h) {
Renderer@ renderer = Manager.Renderer;
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, .3f);
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.DrawImage(img, AABB2(x, y + h, w, 2.f));
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Font@ font = this.Font;
Vector2 textPos = TextOrigin + pos;
string text = Text;
string composition = this.EditingText;
int editStart = this.TextEditingRangeStart;
int editLen = this.TextEditingRangeLength;
int markStart = SelectionStart;
int markEnd = SelectionEnd;
if(composition.length > 0){
this.SelectedText = "";
markStart = SelectionStart + editStart;
markEnd = markStart + editLen;
text = text.substr(0, SelectionStart) + composition + text.substr(SelectionStart);
}
if(text.length == 0){
if(IsEnabled) {
font.Draw(Placeholder, textPos, TextScale, PlaceholderColor);
}
}else{
font.Draw(text, textPos, TextScale, IsEnabled ? TextColor : DisabledTextColor);
}
if(IsFocused){
float fontHeight = font.Measure("A").y;
// draw selection
int start = markStart;
int end = markEnd;
if(end == start) {
float x = font.Measure(text.substr(0, start)).x;
DrawBeam(x + textPos.x, textPos.y, fontHeight);
} else {
float x1 = font.Measure(text.substr(0, start)).x;
float x2 = font.Measure(text.substr(0, end)).x;
DrawHighlight(textPos.x + x1, textPos.y, x2 - x1, fontHeight);
}
// draw composition underline
if(composition.length > 0) {
start = SelectionStart;
end = start + composition.length;
float x1 = font.Measure(text.substr(0, start)).x;
float x2 = font.Measure(text.substr(0, end)).x;
DrawEditingLine(textPos.x + x1, textPos.y, x2 - x1, fontHeight);
}
}
UIElement::Render();
}
}
class Field: FieldBase {
private bool hover;
Field(UIManager@ manager) {
super(manager);
TextOrigin = Vector2(2.f, 2.f);
}
void MouseEnter() {
hover = true;
FieldBase::MouseEnter();
}
void MouseLeave() {
hover = false;
FieldBase::MouseLeave();
}
void Render() {
// render background
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.ColorNP = Vector4(0.f, 0.f, 0.f, IsFocused ? 0.3f : 0.1f);
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
if(IsFocused) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
}else if(hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.1f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.06f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x, pos.y + size.y - 1.f, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x, pos.y + 1.f, 1.f, size.y - 2.f));
renderer.DrawImage(img, AABB2(pos.x + size.x - 1.f, pos.y + 1.f, 1.f, size.y - 2.f));
FieldBase::Render();
}
}
enum ScrollBarOrientation {
Horizontal,
Vertical
}
class ScrollBarBase: UIElement {
double MinValue = 0.0;
double MaxValue = 100.0;
double Value = 0.0;
double SmallChange = 1.0;
double LargeChange = 20.0;
EventHandler@ Changed;
ScrollBarBase(UIManager@ manager) {
super(manager);
}
void ScrollBy(double delta) {
ScrollTo(Value + delta);
}
void ScrollTo(double val) {
val = Clamp(val, MinValue, MaxValue);
if(val == Value) {
return;
}
Value = val;
OnChanged();
}
void OnChanged() {
if(Changed !is null) {
Changed(this);
}
}
ScrollBarOrientation Orientation {
get {
if(Size.x > Size.y) {
return spades::ui::ScrollBarOrientation::Horizontal;
} else {
return spades::ui::ScrollBarOrientation::Vertical;
}
}
}
}
class ScrollBarTrackBar: UIElement {
private ScrollBar@ scrollBar;
private bool dragging = false;
private double startValue;
private float startCursorPos;
private bool hover = false;
ScrollBarTrackBar(ScrollBar@ scrollBar) {
super(scrollBar.Manager);
@this.scrollBar = scrollBar;
IsMouseInteractive = true;
}
private float GetCursorPos(Vector2 pos) {
if(scrollBar.Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
return pos.x + Position.x;
} else {
return pos.y + Position.y;
}
}
void MouseDown(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
if(scrollBar.TrackBarMovementRange < 0.0001f) {
// immobile
return;
}
dragging = true;
startValue = scrollBar.Value;
startCursorPos = GetCursorPos(clientPosition);
}
void MouseMove(Vector2 clientPosition) {
if(dragging) {
double val = startValue;
float delta = GetCursorPos(clientPosition) - startCursorPos;
val += delta * (scrollBar.MaxValue - scrollBar.MinValue) /
double(scrollBar.TrackBarMovementRange);
scrollBar.ScrollTo(val);
}
}
void MouseUp(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
dragging = false;
}
void MouseEnter() {
hover = true;
UIElement::MouseEnter();
}
void MouseLeave() {
hover = false;
UIElement::MouseLeave();
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if(scrollBar.Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
pos.y += 4.f; size.y -= 8.f;
} else {
pos.x += 4.f; size.x -= 8.f;
}
if(dragging) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.4f);
} else if (hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.1f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
}
}
class ScrollBarFill: ButtonBase {
private ScrollBarBase@ scrollBar;
private bool up;
ScrollBarFill(ScrollBarBase@ scrollBar, bool up) {
super(scrollBar.Manager);
@this.scrollBar = scrollBar;
IsMouseInteractive = true;
Repeat = true;
this.up = up;
}
void PlayMouseEnterSound() {
// suppress
}
void PlayActivateSound() {
// suppress
}
void Render() {
// nothing to draw
}
}
class ScrollBarButton: ButtonBase {
private ScrollBar@ scrollBar;
private bool up;
private Image@ image;
ScrollBarButton(ScrollBar@ scrollBar, bool up) {
super(scrollBar.Manager);
@this.scrollBar = scrollBar;
IsMouseInteractive = true;
Repeat = true;
this.up = up;
@image = Manager.Renderer.RegisterImage("Gfx/UI/ScrollArrow.png");
}
void PlayMouseEnterSound() {
// suppress
}
void PlayActivateSound() {
// suppress
}
void Render() {
Renderer@ r = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
pos += size * 0.5f;
float siz = image.Width * 0.5f;
AABB2 srcRect(0.f, 0.f, image.Width, image.Height);
if(Pressed and Hover) {
r.ColorNP = Vector4(1.f, 1.f, 1.f, 0.6f);
} else if (Hover) {
r.ColorNP = Vector4(1.f, 1.f, 1.f, 0.4f);
} else {
r.ColorNP = Vector4(1.f, 1.f, 1.f, 0.2f);
}
if(scrollBar.Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
if(up) {
r.DrawImage(image,
Vector2(pos.x + siz, pos.y - siz), Vector2(pos.x + siz, pos.y + siz), Vector2(pos.x - siz, pos.y - siz),
srcRect);
} else {
r.DrawImage(image,
Vector2(pos.x - siz, pos.y + siz), Vector2(pos.x - siz, pos.y - siz), Vector2(pos.x + siz, pos.y + siz),
srcRect);
}
} else {
if(up) {
r.DrawImage(image,
Vector2(pos.x + siz, pos.y + siz), Vector2(pos.x - siz, pos.y + siz), Vector2(pos.x + siz, pos.y - siz),
srcRect);
} else {
r.DrawImage(image,
Vector2(pos.x - siz, pos.y - siz), Vector2(pos.x + siz, pos.y - siz), Vector2(pos.x - siz, pos.y + siz),
srcRect);
}
}
}
}
class ScrollBar: ScrollBarBase {
private ScrollBarTrackBar@ trackBar;
private ScrollBarFill@ fill1;
private ScrollBarFill@ fill2;
private ScrollBarButton@ button1;
private ScrollBarButton@ button2;
private float ButtonSize = 16.f;
ScrollBar(UIManager@ manager) {
super(manager);
@trackBar = ScrollBarTrackBar(this);
AddChild(trackBar);
@fill1 = ScrollBarFill(this, false);
@fill1.Activated = EventHandler(this.LargeDown);
AddChild(fill1);
@fill2 = ScrollBarFill(this, true);
@fill2.Activated = EventHandler(this.LargeUp);
AddChild(fill2);
@button1 = ScrollBarButton(this, false);
@button1.Activated = EventHandler(this.SmallDown);
AddChild(button1);
@button2 = ScrollBarButton(this, true);
@button2.Activated = EventHandler(this.SmallUp);
AddChild(button2);
}
private void LargeDown(UIElement@ e) {
ScrollBy(-LargeChange);
}
private void LargeUp(UIElement@ e) {
ScrollBy(LargeChange);
}
private void SmallDown(UIElement@ e) {
ScrollBy(-SmallChange);
}
private void SmallUp(UIElement@ e) {
ScrollBy(SmallChange);
}
void OnChanged() {
Layout();
ScrollBarBase::OnChanged();
Layout();
}
void Layout() {
Vector2 size = Size;
float tPos = TrackBarPosition;
float tLen = TrackBarLength;
if(Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
button1.Bounds = AABB2(0.f, 0.f, ButtonSize, size.y);
button2.Bounds = AABB2(size.x - ButtonSize, 0.f, ButtonSize, size.y);
fill1.Bounds = AABB2(ButtonSize, 0.f, tPos - ButtonSize, size.y);
fill2.Bounds = AABB2(tPos + tLen, 0.f, size.x - ButtonSize - tPos - tLen, size.y);
trackBar.Bounds = AABB2(tPos, 0.f, tLen, size.y);
} else {
button1.Bounds = AABB2(0.f, 0.f, size.x, ButtonSize);
button2.Bounds = AABB2(0.f, size.y - ButtonSize, size.x, ButtonSize);
fill1.Bounds = AABB2(0.f, ButtonSize, size.x, tPos - ButtonSize);
fill2.Bounds = AABB2(0.f, tPos + tLen, size.x, size.y - ButtonSize - tPos - tLen);
trackBar.Bounds = AABB2(0.f, tPos, size.x, tLen);
}
}
void OnResized() {
Layout();
UIElement::OnResized();
}
float Length {
get {
if(Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
return Size.x;
} else {
return Size.y;
}
}
}
float TrackBarAreaLength {
get {
return Length - ButtonSize - ButtonSize;
}
}
float TrackBarLength {
get {
return Max(TrackBarAreaLength *
(LargeChange / (MaxValue - MinValue + LargeChange)), 40.f);
}
}
float TrackBarMovementRange {
get {
return TrackBarAreaLength - TrackBarLength;
}
}
float TrackBarPosition {
get {
if(MaxValue == MinValue) {
return ButtonSize;
}
return float((Value - MinValue) / (MaxValue - MinValue) * TrackBarMovementRange) + ButtonSize;
}
}
void Render() {
Layout();
ScrollBarBase::Render();
}
}
class SliderKnob: UIElement {
private Slider@ slider;
private bool dragging = false;
private double startValue;
private float startCursorPos;
private bool hover = false;
SliderKnob(Slider@ slider) {
super(slider.Manager);
@this.slider = slider;
IsMouseInteractive = true;
}
private float GetCursorPos(Vector2 pos) {
return pos.x + Position.x;
}
void MouseDown(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
dragging = true;
startValue = slider.Value;
startCursorPos = GetCursorPos(clientPosition);
}
void MouseMove(Vector2 clientPosition) {
if(dragging) {
double val = startValue;
float delta = GetCursorPos(clientPosition) - startCursorPos;
val += delta * (slider.MaxValue - slider.MinValue) /
double(slider.TrackBarMovementRange);
slider.ScrollTo(val);
}
}
void MouseUp(MouseButton button, Vector2 clientPosition) {
if(button != spades::ui::MouseButton::LeftMouseButton) {
return;
}
dragging = false;
}
void MouseEnter() {
hover = true;
UIElement::MouseEnter();
}
void MouseLeave() {
hover = false;
UIElement::MouseLeave();
}
void Render() {
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if (hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.5f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.3f);
}
renderer.DrawImage(img,
AABB2(pos.x + size.x * 0.5f - 3.f, pos.y,
6.f, size.y));
renderer.ColorNP = Vector4(0.f, 0.f, 0.f, 0.6f);
renderer.DrawImage(img,
AABB2(pos.x + size.x * 0.5f - 2.f, pos.y + 1.f,
4.f, size.y - 2.f));
}
}
class Slider: ScrollBarBase {
private SliderKnob@ knob;
private ScrollBarFill@ fill1;
private ScrollBarFill@ fill2;
Slider(UIManager@ manager) {
super(manager);
@knob = SliderKnob(this);
AddChild(knob);
@fill1 = ScrollBarFill(this, false);
@fill1.Activated = EventHandler(this.LargeDown);
AddChild(fill1);
@fill2 = ScrollBarFill(this, true);
@fill2.Activated = EventHandler(this.LargeUp);
AddChild(fill2);
}
private void LargeDown(UIElement@ e) {
ScrollBy(-LargeChange);
}
private void LargeUp(UIElement@ e) {
ScrollBy(LargeChange);
}/*
private void SmallDown(UIElement@ e) {
ScrollBy(-SmallChange);
}
private void SmallUp(UIElement@ e) {
ScrollBy(SmallChange);
}*/
void OnChanged() {
Layout();
ScrollBarBase::OnChanged();
Layout();
}
void Layout() {
Vector2 size = Size;
float tPos = TrackBarPosition;
float tLen = TrackBarLength;
fill1.Bounds = AABB2(0.f, 0.f, tPos, size.y);
fill2.Bounds = AABB2(tPos + tLen, 0.f, size.x - tPos - tLen, size.y);
knob.Bounds = AABB2(tPos, 0.f, tLen, size.y);
}
void OnResized() {
Layout();
UIElement::OnResized();
}
float Length {
get {
if(Orientation == spades::ui::ScrollBarOrientation::Horizontal) {
return Size.x;
} else {
return Size.y;
}
}
}
float TrackBarAreaLength {
get {
return Length;
}
}
float TrackBarLength {
get {
return 16.f;
}
}
float TrackBarMovementRange {
get {
return TrackBarAreaLength - TrackBarLength;
}
}
float TrackBarPosition {
get {
return float((Value - MinValue) / (MaxValue - MinValue) * TrackBarMovementRange);
}
}
void Render() {
Layout();
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.1f);
renderer.DrawImage(img,
AABB2(pos.x, pos.y + size.y * 0.5f - 3.f,
size.x, 6.f));
renderer.ColorNP = Vector4(0.f, 0.f, 0.f, 0.2f);
renderer.DrawImage(img,
AABB2(pos.x + 1.f, pos.y + size.y * 0.5f - 2.f,
size.x - 2.f, 4.f));
ScrollBarBase::Render();
}
}
class ListViewModel {
int NumRows { get { return 0; } }
UIElement@ CreateElement(int row) { return null; }
void RecycleElement(UIElement@ elem) {}
}
/** Simple virtual stack panel implementation. */
class ListViewBase: UIElement {
private ScrollBar@ scrollBar;
private ListViewModel@ model;
float RowHeight = 24.f;
float ScrollBarWidth = 16.f;
private UIElementDeque items;
private int loadedStartIndex = 0;
ListViewBase(UIManager@ manager) {
super(manager);
@scrollBar = ScrollBar(Manager);
scrollBar.Bounds = AABB2();
AddChild(scrollBar);
IsMouseInteractive = true;
@scrollBar.Changed = EventHandler(this.OnScrolled);
@model = ListViewModel();
}
private void OnScrolled(UIElement@ sender) {
Layout();
}
int NumVisibleRows {
get final {
return int(floor(Size.y / RowHeight));
}
}
int MaxTopRowIndex {
get final {
return Max(0, model.NumRows - NumVisibleRows);
}
}
int TopRowIndex {
get final {
int idx = int(floor(scrollBar.Value + 0.5));
return Clamp(idx, 0, MaxTopRowIndex);
}
}
void OnResized() {
Layout();
UIElement::OnResized();
}
void Layout() {
scrollBar.MaxValue = double(MaxTopRowIndex);
scrollBar.ScrollTo(scrollBar.Value); // ensures value is in range
scrollBar.LargeChange = double(NumVisibleRows);
int numRows = model.NumRows;
// load items
int visibleStart = TopRowIndex;
int visibleEnd = Min(visibleStart + NumVisibleRows, numRows);
int loadedStart = loadedStartIndex;
int loadedEnd = loadedStartIndex + items.Count;
if(items.Count == 0 or visibleStart >= loadedEnd or visibleEnd <= loadedStart) {
// full reload
UnloadAll();
for(int i = visibleStart; i < visibleEnd; i++) {
items.PushBack(model.CreateElement(i));
AddChild(items.Back);
}
loadedStartIndex = visibleStart;
} else {
while(loadedStart < visibleStart) {
RemoveChild(items.Front);
model.RecycleElement(items.Front);
items.PopFront();
loadedStart++;
}
while(loadedEnd > visibleEnd) {
RemoveChild(items.Back);
model.RecycleElement(items.Back);
items.PopBack();
loadedEnd--;
}
while(visibleStart < loadedStart) {
loadedStart--;
items.PushFront(model.CreateElement(loadedStart));
AddChild(items.Front);
}
while(visibleEnd > loadedEnd) {
items.PushBack(model.CreateElement(loadedEnd));
AddChild(items.Back);
loadedEnd++;
}
loadedStartIndex = loadedStart;
}
// relayout items
UIElementDeque@ items = this.items;
int count = items.Count;
float y = 0.f;
float w = ItemWidth;
for(int i = 0; i < count; i++){
items[i].Bounds = AABB2(0.f, y, w, RowHeight);
y += RowHeight;
}
// move scroll bar
scrollBar.Bounds = AABB2(Size.x - ScrollBarWidth, 0.f, ScrollBarWidth, Size.y);
}
float ItemWidth {
get {
return Size.x - ScrollBarWidth;
}
}
void MouseWheel(float delta) {
scrollBar.ScrollBy(delta);
}
void Reload() {
UnloadAll();
Layout();
}
private void UnloadAll() {
UIElementDeque@ items = this.items;
int count = items.Count;
for(int i = 0; i < count; i++){
RemoveChild(items[i]);
model.RecycleElement(items[i]);
}
items.Clear();
}
ListViewModel@ Model {
get final { return model; }
set {
if(model is value) {
return;
}
UnloadAll();
@model = value;
Layout();
}
}
void ScrollToTop() {
scrollBar.ScrollTo(0.0);
}
void ScrollToEnd() {
scrollBar.ScrollTo(scrollBar.MaxValue);
}
}
class TextViewerModel: ListViewModel {
UIManager@ manager;
string[]@ lines = array<string>();
Vector4[]@ colors = array<spades::Vector4>();
Font@ font;
float width;
void AddLine(string text, Vector4 color) {
int startPos = 0;
if(font.Measure(text).x <= width) {
lines.insertLast(text);
colors.insertLast(color);
return;
}
int pos = 0;
int len = int(text.length);
bool charMode = false;
while(startPos < len) {
int nextPos = pos + 1;
if(charMode) {
// skip to the next UTF-8 character boundary
while(nextPos < len && ((text[nextPos] & 0x80) != 0) &&
((text[nextPos] & 0xc0) != 0xc0))
nextPos++;
} else {
while(nextPos < len && text[nextPos] != 0x20)
nextPos++;
}
if(font.Measure(text.substr(startPos, nextPos - startPos)).x > width) {
if(pos == startPos) {
if(charMode) {
pos = nextPos;
}else{
charMode = true;
}
continue;
}else{
lines.insertLast(text.substr(startPos, pos - startPos));
colors.insertLast(color);
startPos = pos;
while(startPos < len && text[startPos] == 0x20)
startPos++;
pos = startPos;
charMode = false;
continue;
}
}else{
pos = nextPos;
if(nextPos >= len) {
lines.insertLast(text.substr(startPos, nextPos - startPos));
colors.insertLast(color);
break;
}
}
}
}
TextViewerModel(UIManager@ manager, string text, Font@ font, float width) {
@this.manager = manager;
@this.font = font;
this.width = width;
string[]@ lines = text.split("\n");
for(uint i = 0; i < lines.length; i++)
AddLine(lines[i], Vector4(1.f, 1.f, 1.f, 1.f));
}
int NumRows { get { return int(lines.length); } }
UIElement@ CreateElement(int row) {
Label i(manager);
i.Text = lines[row];
i.TextColor = colors[row];
return i;
}
void RecycleElement(UIElement@ elem) {}
}
class ListView: ListViewBase {
ListView(UIManager@ manager) {
super(manager);
}
void Render() {
// render background
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.ColorNP = Vector4(0.f, 0.f, 0.f, 0.2f);
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.06f);
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x, pos.y + size.y - 1.f, size.x, 1.f));
renderer.DrawImage(img, AABB2(pos.x, pos.y + 1.f, 1.f, size.y - 2.f));
renderer.DrawImage(img, AABB2(pos.x + size.x - 1.f, pos.y + 1.f, 1.f, size.y - 2.f));
ListViewBase::Render();
}
}
class TextViewer: ListViewBase {
private string text;
private TextViewerModel@ textmodel;
TextViewer(UIManager@ manager) {
super(manager);
}
/** Sets the displayed text. Ensure TextViewer.Font is not null before setting this proeprty. */
string Text {
get final { return text; }
set {
text = value;
@textmodel = TextViewerModel(Manager, text, Font, ItemWidth);
@Model = textmodel;
}
}
void AddLine(string line, bool autoscroll = false, Vector4 color = Vector4(1.f, 1.f, 1.f, 1.f)) {
if(textmodel is null) {
this.Text = "";
}
if(autoscroll){
this.Layout();
if(this.scrollBar.Value < this.scrollBar.MaxValue) {
autoscroll = false;
}
}
textmodel.AddLine(line, color);
if(autoscroll) {
this.Layout();
this.ScrollToEnd();
}
}
}
class SimpleTabStripItem: ButtonBase {
UIElement@ linkedElement;
SimpleTabStripItem(UIManager@ manager, UIElement@ linkedElement) {
super(manager);
@this.linkedElement = linkedElement;
}
void MouseDown(MouseButton button, Vector2 clientPosition) {
PlayActivateSound();
OnActivated();
}
void Render() {
this.Toggled = linkedElement.Visible;
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Vector4 textColor(0.9f, 0.9f, 0.9f, 1.0f);
Image@ img = renderer.RegisterImage("Gfx/White.tga");
if(Toggled) {
renderer.ColorNP = Vector4(0.9f, 0.9f, 0.9f, 1.0f);
textColor = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
} else if(Hover) {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.3f);
} else {
renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 0.0f);
}
renderer.DrawImage(img, AABB2(pos.x, pos.y, size.x, size.y));
Vector2 txtSize = Font.Measure(Caption);
Font.Draw(Caption, pos + (size - txtSize) * 0.5f, 1.f, textColor);
}
}
class SimpleTabStrip: UIElement {
private float nextX = 0.f;
EventHandler@ Changed;
SimpleTabStrip(UIManager@ manager) {
super(manager);
}
private void OnChanged() {
if(Changed !is null) {
Changed(this);
}
}
private void OnItemActivated(UIElement@ sender) {
SimpleTabStripItem@ item = cast<SimpleTabStripItem>(sender);
UIElement@ linked = item.linkedElement;
UIElement@[]@ children = this.GetChildren();
for(uint i = 0, count = children.length; i < count; i++) {
SimpleTabStripItem@ otherItem = cast<SimpleTabStripItem>(children[i]);
otherItem.linkedElement.Visible = (otherItem.linkedElement is linked);
}
OnChanged();
}
void AddItem(string title, UIElement@ linkedElement) {
SimpleTabStripItem item(this.Manager, linkedElement);
item.Caption = title;
float w = this.Font.Measure(title).x + 18.f;
item.Bounds = AABB2(nextX, 0.f, w, 24.f);
nextX += w + 4.f;
@item.Activated = EventHandler(this.OnItemActivated);
this.AddChild(item);
}
void Render() {
UIElement::Render();
Renderer@ renderer = Manager.Renderer;
Vector2 pos = ScreenPosition;
Vector2 size = Size;
Image@ img = renderer.RegisterImage("Gfx/White.tga");
renderer.ColorNP = Vector4(0.9f, 0.9f, 0.9f, 1.0f);
renderer.DrawImage(img, AABB2(pos.x, pos.y + 24.f, size.x, 1.f));
}
}
}
}