ppsspp/ui/view.cpp

845 lines
23 KiB
C++
Raw Normal View History

#include <queue>
2013-08-23 00:53:52 +02:00
#include <algorithm>
2013-05-03 00:21:39 +02:00
#include "base/mutex.h"
#include "input/input_state.h"
2013-07-18 10:25:30 +02:00
#include "input/keycodes.h"
2013-05-03 00:21:39 +02:00
#include "gfx_es2/draw_buffer.h"
#include "gfx/texture.h"
2013-05-03 00:21:39 +02:00
#include "gfx/texture_atlas.h"
#include "util/text/utf8.h"
2013-05-03 00:21:39 +02:00
#include "ui/ui.h"
#include "ui/view.h"
2013-05-28 00:32:00 +02:00
#include "ui/ui_context.h"
2013-05-03 00:21:39 +02:00
namespace UI {
static View *focusedView;
static bool focusMovementEnabled;
2013-05-03 00:21:39 +02:00
static recursive_mutex mutex_;
2013-05-28 00:50:19 +02:00
const float ITEM_HEIGHT = 64.f;
2013-05-03 00:21:39 +02:00
struct DispatchQueueItem {
Event *e;
EventParams params;
};
std::deque<DispatchQueueItem> g_dispatchQueue;
void EventTriggered(Event *e, EventParams params) {
lock_guard guard(mutex_);
DispatchQueueItem item;
item.e = e;
item.params = params;
g_dispatchQueue.push_front(item);
}
void DispatchEvents() {
lock_guard guard(mutex_);
while (!g_dispatchQueue.empty()) {
DispatchQueueItem item = g_dispatchQueue.back();
g_dispatchQueue.pop_back();
if (item.e) {
item.e->Dispatch(item.params);
}
}
}
void RemoveQueuedEvents(View *v) {
2013-10-19 14:29:32 -07:00
for (size_t i = 0; i < g_dispatchQueue.size(); i++) {
if (g_dispatchQueue[i].params.v == v)
g_dispatchQueue.erase(g_dispatchQueue.begin() + i);
}
}
2013-05-03 00:21:39 +02:00
View *GetFocusedView() {
return focusedView;
}
void SetFocusedView(View *view) {
2013-07-15 17:51:12 +02:00
if (focusedView) {
focusedView->FocusChanged(FF_LOSTFOCUS);
}
2013-05-03 00:21:39 +02:00
focusedView = view;
2013-07-15 17:51:12 +02:00
if (focusedView) {
focusedView->FocusChanged(FF_GOTFOCUS);
}
2013-05-03 00:21:39 +02:00
}
void EnableFocusMovement(bool enable) {
focusMovementEnabled = enable;
if (!enable) {
if (focusedView)
focusedView->FocusChanged(FF_LOSTFOCUS);
focusedView = 0;
}
}
bool IsFocusMovementEnabled() {
return focusMovementEnabled;
}
2013-05-03 00:21:39 +02:00
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured) {
*measured = sz;
if (sz == WRAP_CONTENT) {
if (spec.type == UNSPECIFIED || spec.type == AT_MOST)
*measured = contentWidth;
else if (spec.type == EXACTLY)
*measured = spec.size;
} else if (sz == FILL_PARENT) {
if (spec.type == UNSPECIFIED)
*measured = contentWidth; // We have no value to set
2013-05-03 00:21:39 +02:00
else
*measured = spec.size;
} else if (spec.type == EXACTLY || (spec.type == AT_MOST && *measured > spec.size)) {
*measured = spec.size;
}
}
2013-06-02 22:25:57 +10:00
void Event::Add(std::function<EventReturn(EventParams&)> func) {
2013-05-03 00:21:39 +02:00
HandlerRegistration reg;
reg.func = func;
handlers_.push_back(reg);
}
// Call this from input thread or whatever, it doesn't matter
2013-06-01 18:59:03 +02:00
void Event::Trigger(EventParams &e) {
EventTriggered(this, e);
2013-05-03 00:21:39 +02:00
}
// Call this from UI thread
EventReturn Event::Dispatch(EventParams &e) {
for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) {
if ((iter->func)(e) == UI::EVENT_DONE) {
// Event is handled, stop looping immediately. This event might even have gotten deleted.
return UI::EVENT_DONE;
2013-05-03 00:21:39 +02:00
}
}
return UI::EVENT_SKIPPED;
}
View::~View() {
if (HasFocus())
SetFocusedView(0);
RemoveQueuedEvents(this);
2013-05-03 00:21:39 +02:00
}
2013-05-28 00:32:00 +02:00
void View::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
2013-05-03 00:21:39 +02:00
float contentW = 0.0f, contentH = 0.0f;
GetContentDimensions(dc, contentW, contentH);
MeasureBySpec(layoutParams_->width, contentW, horiz, &measuredWidth_);
MeasureBySpec(layoutParams_->height, contentH, vert, &measuredHeight_);
}
// Default values
2013-05-28 00:32:00 +02:00
void View::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
2013-05-03 00:21:39 +02:00
w = 10.0f;
h = 10.0f;
}
Point View::GetFocusPosition(FocusDirection dir) {
2013-07-17 01:03:29 +02:00
// The +2/-2 is some extra fudge factor to cover for views sitting right next to each other.
// Distance zero yields strange results otherwise.
switch (dir) {
2013-07-17 01:03:29 +02:00
case FOCUS_LEFT: return Point(bounds_.x + 2, bounds_.centerY());
case FOCUS_RIGHT: return Point(bounds_.x2() - 2, bounds_.centerY());
case FOCUS_UP: return Point(bounds_.centerX(), bounds_.y + 2);
case FOCUS_DOWN: return Point(bounds_.centerX(), bounds_.y2() - 2);
default:
return bounds_.Center();
}
}
2014-03-03 12:33:57 +01:00
bool View::SetFocus() {
if (IsFocusMovementEnabled()) {
if (CanBeFocused()) {
SetFocusedView(this);
return true;
}
}
return false;
2014-03-03 12:33:57 +01:00
}
2013-05-03 00:21:39 +02:00
void Clickable::Click() {
UI::EventParams e;
e.v = this;
OnClick.Trigger(e);
};
2013-07-15 17:51:12 +02:00
void Clickable::FocusChanged(int focusFlags) {
if (focusFlags & FF_LOSTFOCUS) {
down_ = false;
dragging_ = false;
}
}
2013-05-03 00:21:39 +02:00
void Clickable::Touch(const TouchInput &input) {
if (!IsEnabled()) {
2013-06-01 18:59:03 +02:00
dragging_ = false;
down_ = false;
return;
}
2013-05-25 16:52:27 +02:00
if (input.flags & TOUCH_DOWN) {
2013-05-03 00:21:39 +02:00
if (bounds_.Contains(input.x, input.y)) {
if (IsFocusMovementEnabled())
SetFocusedView(this);
dragging_ = true;
down_ = true;
2013-05-03 00:21:39 +02:00
} else {
down_ = false;
2013-05-25 16:52:27 +02:00
dragging_ = false;
2013-05-03 00:21:39 +02:00
}
2013-05-25 16:52:27 +02:00
} else if (input.flags & TOUCH_MOVE) {
if (dragging_)
down_ = bounds_.Contains(input.x, input.y);
}
2013-05-03 00:21:39 +02:00
if (input.flags & TOUCH_UP) {
if ((input.flags & TOUCH_CANCEL) == 0 && dragging_ && bounds_.Contains(input.x, input.y)) {
2013-05-03 00:21:39 +02:00
Click();
}
down_ = false;
downCountDown_ = 0;
2013-05-25 16:52:27 +02:00
dragging_ = false;
2013-05-03 00:21:39 +02:00
}
}
// TODO: O/X confirm preference for xperia play?
bool IsAcceptKeyCode(int keyCode) {
if (confirmKeys.empty()) {
return keyCode == NKCODE_SPACE || keyCode == NKCODE_ENTER || keyCode == NKCODE_Z || keyCode == NKCODE_BUTTON_A || keyCode == NKCODE_BUTTON_CROSS || keyCode == NKCODE_BUTTON_1;
} else {
return std::find(confirmKeys.begin(), confirmKeys.end(), (keycode_t)keyCode) != confirmKeys.end();
}
2013-07-08 12:34:39 +02:00
}
bool IsEscapeKeyCode(int keyCode) {
if (cancelKeys.empty()) {
return keyCode == NKCODE_ESCAPE || keyCode == NKCODE_BACK || keyCode == NKCODE_BUTTON_CIRCLE || keyCode == NKCODE_BUTTON_B || keyCode == NKCODE_BUTTON_2;
} else {
return std::find(cancelKeys.begin(), cancelKeys.end(), (keycode_t)keyCode) != cancelKeys.end();
}
}
bool IsTabLeftKeyCode(int keyCode) {
if (tabLeftKeys.empty()) {
return keyCode == NKCODE_BUTTON_L1;
} else {
return std::find(tabLeftKeys.begin(), tabLeftKeys.end(), (keycode_t)keyCode) != tabLeftKeys.end();
}
}
bool IsTabRightKeyCode(int keyCode) {
if (tabRightKeys.empty()) {
return keyCode == NKCODE_BUTTON_R1;
} else {
return std::find(tabRightKeys.begin(), tabRightKeys.end(), (keycode_t)keyCode) != tabRightKeys.end();
}
}
2013-07-08 12:34:39 +02:00
void Clickable::Key(const KeyInput &key) {
if (!HasFocus() && key.deviceId != DEVICE_ID_MOUSE) {
2013-06-02 23:44:28 +02:00
down_ = false;
return;
}
// TODO: Replace most of Update with this.
if (key.flags & KEY_DOWN) {
if (IsAcceptKeyCode(key.keyCode)) {
down_ = true;
}
}
if (key.flags & KEY_UP) {
if (IsAcceptKeyCode(key.keyCode)) {
if (down_) {
Click();
2013-08-13 00:06:23 +02:00
down_ = false;
}
2013-08-13 00:06:23 +02:00
} else if (IsEscapeKeyCode(key.keyCode)) {
down_ = false;
}
}
}
void StickyChoice::Touch(const TouchInput &input) {
dragging_ = false;
if (!IsEnabled()) {
down_ = false;
return;
}
if (input.flags & TOUCH_DOWN) {
if (bounds_.Contains(input.x, input.y)) {
if (IsFocusMovementEnabled())
SetFocusedView(this);
down_ = true;
Click();
}
}
2013-05-03 00:21:39 +02:00
}
void StickyChoice::Key(const KeyInput &key) {
if (!HasFocus()) {
return;
}
// TODO: Replace most of Update with this.
if (key.flags & KEY_DOWN) {
if (IsAcceptKeyCode(key.keyCode)) {
down_ = true;
Click();
}
}
}
void StickyChoice::FocusChanged(int focusFlags) {
// Override Clickable's FocusChanged to do nothing.
}
2013-05-28 00:50:19 +02:00
Item::Item(LayoutParams *layoutParams) : InertView(layoutParams) {
if (!layoutParams) {
layoutParams_->width = FILL_PARENT;
layoutParams_->height = ITEM_HEIGHT;
}
2013-05-28 00:50:19 +02:00
}
void Item::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
w = 0.0f;
h = 0.0f;
}
void ClickableItem::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
w = 0.0f;
h = 0.0f;
}
2013-05-28 00:50:19 +02:00
ClickableItem::ClickableItem(LayoutParams *layoutParams) : Clickable(layoutParams) {
if (!layoutParams) {
if (layoutParams_->width == WRAP_CONTENT)
layoutParams_->width = FILL_PARENT;
layoutParams_->height = ITEM_HEIGHT;
}
2013-05-28 00:50:19 +02:00
}
2013-05-28 00:32:00 +02:00
void ClickableItem::Draw(UIContext &dc) {
2013-08-20 00:34:54 +02:00
Style style = dc.theme->itemStyle;
2013-08-16 16:47:25 +02:00
if (HasFocus()) {
style = dc.theme->itemFocusedStyle;
2013-05-25 16:52:27 +02:00
}
2013-08-16 16:47:25 +02:00
if (down_) {
style = dc.theme->itemDownStyle;
2013-08-20 00:34:54 +02:00
}
dc.FillRect(style.background, bounds_);
2013-05-25 16:52:27 +02:00
}
void Choice::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
2013-08-20 00:34:54 +02:00
if (atlasImage_ != -1) {
const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
w = img.w;
h = img.h;
} else {
dc.MeasureText(dc.theme->uiFont, text_.c_str(), &w, &h);
2013-08-20 00:34:54 +02:00
}
2013-08-16 16:47:25 +02:00
w += 24;
h += 16;
}
void Choice::HighlightChanged(bool highlighted){
highlighted_ = highlighted;
}
2013-05-28 00:32:00 +02:00
void Choice::Draw(UIContext &dc) {
if (!IsSticky()) {
ClickableItem::Draw(dc);
} else {
Style style = dc.theme->itemStyle;
if (highlighted_) {
style = dc.theme->itemHighlightedStyle;
}
if (down_) {
style = dc.theme->itemDownStyle;
}
if (HasFocus()) {
style = dc.theme->itemFocusedStyle;
}
dc.FillRect(style.background, bounds_);
}
Style style = dc.theme->itemStyle;
if (!IsEnabled()) {
style = dc.theme->itemDisabledStyle;
}
2013-08-20 00:34:54 +02:00
if (atlasImage_ != -1) {
dc.Draw()->DrawImage(atlasImage_, bounds_.centerX(), bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
} else {
int paddingX = 12;
dc.SetFontStyle(dc.theme->uiFont);
2013-08-30 22:42:02 +02:00
if (centered_) {
dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), style.fgColor, ALIGN_CENTER);
} else {
if (iconImage_ != -1) {
dc.Draw()->DrawImage(iconImage_, bounds_.x2() - 32 - paddingX, bounds_.centerY(), 0.5f, style.fgColor, ALIGN_CENTER);
}
2013-08-30 22:42:02 +02:00
dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
}
2013-08-20 00:34:54 +02:00
}
2013-07-16 00:25:08 +02:00
if (selected_) {
dc.Draw()->DrawImage(dc.theme->checkOn, bounds_.x2() - 40, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
2013-07-16 00:25:08 +02:00
}
2013-05-03 00:21:39 +02:00
}
2013-05-28 00:32:00 +02:00
void InfoItem::Draw(UIContext &dc) {
Item::Draw(dc);
2013-08-16 16:47:25 +02:00
int paddingX = 12;
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER);
dc.DrawText(rightText_.c_str(), bounds_.x2() - paddingX, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER | ALIGN_RIGHT);
// dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y + 2, dc.theme->itemDownStyle.bgColor);
2013-05-25 16:52:27 +02:00
}
2013-08-20 00:34:54 +02:00
ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams)
: Item(layoutParams), text_(text) {
layoutParams_->width = FILL_PARENT;
layoutParams_->height = 40;
}
2013-05-28 00:32:00 +02:00
void ItemHeader::Draw(UIContext &dc) {
dc.SetFontStyle(dc.theme->uiFontSmall);
dc.DrawText(text_.c_str(), bounds_.x + 4, bounds_.centerY(), 0xFFFFFFFF, ALIGN_LEFT | ALIGN_VCENTER);
dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), 0xFFFFFFFF);
2013-05-25 15:12:46 +02:00
}
2013-08-14 23:29:27 +02:00
void PopupHeader::Draw(UIContext &dc) {
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawText(text_.c_str(), bounds_.x + 12, bounds_.centerY(), dc.theme->popupTitle.fgColor, ALIGN_LEFT | ALIGN_VCENTER);
2013-08-16 16:47:25 +02:00
dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->popupTitle.fgColor);
2013-08-14 23:29:27 +02:00
}
2013-10-27 10:58:16 +05:30
void CheckBox::Toggle(){
if (toggle_)
*toggle_ = !(*toggle_);
2013-10-27 10:58:16 +05:30
};
EventReturn CheckBox::OnClicked(EventParams &e) {
Toggle();
return EVENT_CONTINUE; // It's safe to keep processing events.
}
2013-05-28 00:32:00 +02:00
void CheckBox::Draw(UIContext &dc) {
2013-05-25 16:52:27 +02:00
ClickableItem::Draw(dc);
2013-08-16 16:47:25 +02:00
int paddingX = 12;
int image = *toggle_ ? dc.theme->checkOn : dc.theme->checkOff;
Style style = dc.theme->itemStyle;
if (!IsEnabled())
style = dc.theme->itemDisabledStyle;
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
dc.Draw()->DrawImage(image, bounds_.x2() - paddingX, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
}
2013-05-28 00:32:00 +02:00
void Button::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
if (imageID_ != -1) {
const AtlasImage *img = &dc.Draw()->GetAtlas()->images[imageID_];
w = img->w;
h = img->h;
} else {
dc.MeasureText(dc.theme->uiFont, text_.c_str(), &w, &h);
}
2013-11-26 13:56:56 +01:00
// Add some internal padding to not look totally ugly
w += 16;
h += 8;
2013-05-03 00:21:39 +02:00
}
2013-05-28 00:32:00 +02:00
void Button::Draw(UIContext &dc) {
Style style = dc.theme->buttonStyle;
if (HasFocus()) style = dc.theme->buttonFocusedStyle;
if (down_) style = dc.theme->buttonDownStyle;
if (!IsEnabled()) style = dc.theme->buttonDisabledStyle;
// dc.Draw()->DrawImage4Grid(style.image, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), style.bgColor);
dc.FillRect(style.background, bounds_);
float tw, th;
dc.MeasureText(dc.theme->uiFont, text_.c_str(), &tw, &th);
if (tw > bounds_.w || imageID_ != -1) {
dc.PushScissor(bounds_);
}
dc.SetFontStyle(dc.theme->uiFont);
if (imageID_ != -1 && text_.empty()) {
dc.Draw()->DrawImage(imageID_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
} else if (!text_.empty()) {
dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), style.fgColor, ALIGN_CENTER);
if (imageID_ != -1) {
const AtlasImage &img = dc.Draw()->GetAtlas()->images[imageID_];
dc.Draw()->DrawImage(imageID_, bounds_.centerX() - tw / 2 - 5 - img.w/2, bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
}
}
if (tw > bounds_.w || imageID_ != -1) {
dc.PopScissor();
}
2013-05-03 00:21:39 +02:00
}
2013-05-28 00:32:00 +02:00
void ImageView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
2013-05-03 00:21:39 +02:00
// TODO: involve sizemode
w = img.w;
h = img.h;
}
2013-05-28 00:32:00 +02:00
void ImageView::Draw(UIContext &dc) {
2013-08-20 00:34:54 +02:00
const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
2013-05-03 00:21:39 +02:00
// TODO: involve sizemode
2013-08-20 00:34:54 +02:00
float scale = bounds_.w / img.w;
dc.Draw()->DrawImage(atlasImage_, bounds_.x, bounds_.y, scale, 0xFFFFFFFF, ALIGN_TOPLEFT);
2013-05-03 00:21:39 +02:00
}
void TextureView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
// TODO: involve sizemode
if (texture_) {
w = (float)texture_->Width();
h = (float)texture_->Height();
} else {
w = 16;
h = 16;
}
}
void TextureView::Draw(UIContext &dc) {
// TODO: involve sizemode
if (texture_) {
dc.Flush();
texture_->Bind(0);
dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
dc.Flush();
dc.RebindTexture();
}
}
ImageFileView::ImageFileView(std::string filename, ImageSizeMode sizeMode, LayoutParams *layoutParams)
: InertView(layoutParams), color_(0xFFFFFFFF), sizeMode_(sizeMode) {
texture_ = new Texture();
if (!texture_->Load(filename.c_str())) {
ELOG("Failed to load texture %s", filename.c_str());
}
}
ImageFileView::~ImageFileView() {
delete texture_;
}
void ImageFileView::Draw(UIContext &dc) {
// TODO: involve sizemode
if (texture_) {
dc.Flush();
texture_->Bind(0);
dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
dc.Flush();
dc.RebindTexture();
}
}
void ImageFileView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
// TODO: involve sizemode
if (texture_) {
w = (float)texture_->Width();
h = (float)texture_->Height();
} else {
w = 16;
h = 16;
}
}
2013-05-28 00:32:00 +02:00
void TextView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
dc.MeasureText(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont, text_.c_str(), &w, &h);
2013-05-03 00:21:39 +02:00
}
2013-05-28 00:32:00 +02:00
void TextView::Draw(UIContext &dc) {
dc.SetFontStyle(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont);
dc.DrawTextRect(text_.c_str(), bounds_, 0xFFFFFFFF, textAlign_);
2013-05-03 00:21:39 +02:00
}
TextEdit::TextEdit(const std::string &text, const std::string &placeholderText, LayoutParams *layoutParams)
: View(layoutParams), text_(text), placeholderText_(placeholderText) {
caret_ = (int)text_.size();
}
void TextEdit::Draw(UIContext &dc) {
dc.SetFontStyle(dc.theme->uiFont);
dc.FillRect(HasFocus() ? UI::Drawable(0x80000000) : UI::Drawable(0x30000000), bounds_);
if (text_.empty()) {
if (placeholderText_.size()) {
dc.DrawTextRect(placeholderText_.c_str(), bounds_, 0x50FFFFFF, ALIGN_CENTER);
}
} else {
dc.DrawTextRect(text_.c_str(), bounds_, 0xFFFFFFFF, ALIGN_VCENTER | ALIGN_LEFT);
}
// Hack to find the caret position. Might want to find a better way...
std::string subset = text_.substr(0, caret_);
float w, h;
dc.MeasureText(dc.theme->uiFont, subset.c_str(), &w, &h, ALIGN_VCENTER | ALIGN_LEFT);
float caretX = bounds_.x + w;
dc.FillRect(UI::Drawable(0xFFFFFFFF), Bounds(caretX - 1, bounds_.y + 2, 3, bounds_.h - 4));
}
void TextEdit::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
dc.MeasureText(dc.theme->uiFont, text_.size() ? text_.c_str() : "Wj", &w, &h);
w += 2;
h += 2;
}
void TextEdit::Key(const KeyInput &input) {
if (!HasFocus())
return;
// Process navigation keys. These aren't chars.
if (input.flags & KEY_DOWN) {
switch (input.keyCode) {
case NKCODE_DPAD_LEFT: // ASCII left arrow
caret_--;
break;
case NKCODE_DPAD_RIGHT: // ASCII right arrow
caret_++;
break;
case NKCODE_MOVE_HOME:
case NKCODE_PAGE_UP:
caret_ = 0;
break;
case NKCODE_MOVE_END:
case NKCODE_PAGE_DOWN:
caret_ = (int)text_.size();
break;
case NKCODE_FORWARD_DEL:
if (caret_ < (int)text_.size()) {
text_.erase(text_.begin() + caret_, text_.begin() + caret_ + 1);
}
case NKCODE_DEL:
if (caret_ > 0) {
text_.erase(text_.begin() + caret_ - 1, text_.begin() + caret_);
caret_--;
}
break;
}
if (caret_ < 0) {
caret_ = 0;
}
if (caret_ > (int)text_.size()) {
caret_ = (int)text_.size();
}
}
// Process chars.
if (input.flags & KEY_CHAR) {
int unichar = input.keyCode;
if (unichar >= 0x20) { // Ignore control characters.
// Insert it! (todo: do it with a string insert)
char buf[8];
buf[u8_wc_toutf8(buf, unichar)] = '\0';
for (size_t i = 0; i < strlen(buf); i++) {
text_.insert(text_.begin() + caret_, buf[i]);
caret_++;
}
}
}
}
2013-06-02 23:44:28 +02:00
void ProgressBar::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
dc.MeasureText(dc.theme->uiFont, " 100% ", &w, &h);
2013-06-02 23:44:28 +02:00
}
void ProgressBar::Draw(UIContext &dc) {
char temp[32];
sprintf(temp, "%i%%", (int)(progress_ * 100.0f));
dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x + bounds_.w * progress_, bounds_.y2(), 0xc0c0c0c0);
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawTextRect(temp, bounds_, 0xFFFFFFFF, ALIGN_CENTER);
2013-06-02 23:44:28 +02:00
}
2013-05-27 22:22:35 +02:00
void TriggerButton::Touch(const TouchInput &input) {
if (input.flags & TOUCH_DOWN) {
if (bounds_.Contains(input.x, input.y)) {
down_ |= 1 << input.id;
}
}
if (input.flags & TOUCH_MOVE) {
if (bounds_.Contains(input.x, input.y))
down_ |= 1 << input.id;
else
down_ &= ~(1 << input.id);
}
if (input.flags & TOUCH_UP) {
down_ &= ~(1 << input.id);
}
if (down_ != 0) {
*bitField_ |= bit_;
} else {
*bitField_ &= ~bit_;
}
}
2013-05-28 00:32:00 +02:00
void TriggerButton::Draw(UIContext &dc) {
dc.Draw()->DrawImage(imageBackground_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
dc.Draw()->DrawImage(imageForeground_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
2013-05-27 22:22:35 +02:00
}
2013-05-28 00:32:00 +02:00
void TriggerButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
const AtlasImage &image = dc.Draw()->GetAtlas()->images[imageBackground_];
2013-05-27 22:22:35 +02:00
w = image.w;
h = image.h;
}
2013-07-18 10:25:30 +02:00
void Slider::Key(const KeyInput &input) {
if (HasFocus() && (input.flags & KEY_DOWN)) {
2013-07-18 10:25:30 +02:00
switch (input.keyCode) {
case NKCODE_DPAD_LEFT:
case NKCODE_MINUS:
case NKCODE_NUMPAD_SUBTRACT:
*value_ -= step_;
2013-07-18 10:25:30 +02:00
break;
case NKCODE_DPAD_RIGHT:
case NKCODE_PLUS:
case NKCODE_NUMPAD_ADD:
*value_ += step_;
2013-07-18 10:25:30 +02:00
break;
case NKCODE_PAGE_UP:
*value_ -= step_ * 10;
break;
case NKCODE_PAGE_DOWN:
*value_ += step_ * 10;
break;
case NKCODE_MOVE_HOME:
*value_ = minValue_;
break;
case NKCODE_MOVE_END:
*value_ = maxValue_;
break;
2013-07-18 10:25:30 +02:00
}
Clamp();
}
}
void Slider::Touch(const TouchInput &input) {
Clickable::Touch(input);
2013-07-18 10:25:30 +02:00
if (dragging_ || bounds_.Contains(input.x, input.y)) {
2013-08-20 12:48:48 +02:00
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
2013-07-18 10:25:30 +02:00
*value_ = floorf(relativeX * (maxValue_ - minValue_) + minValue_ + 0.5f);
Clamp();
}
}
void Slider::Clamp() {
if (*value_ < minValue_) *value_ = minValue_;
else if (*value_ > maxValue_) *value_ = maxValue_;
// Clamp the value to be a multiple of the nearest step (e.g. if step == 5, value == 293, it'll round down to 290).
*value_ = *value_ - fmodf(*value_, step_);
2013-07-18 10:25:30 +02:00
}
void Slider::Draw(UIContext &dc) {
2013-08-17 12:06:08 +02:00
bool focus = HasFocus();
uint32_t linecolor = dc.theme->popupTitle.fgColor;
uint32_t knobcolor = (down_ || focus) ? dc.theme->popupTitle.fgColor : 0xFFFFFFFF;
2013-08-20 12:48:48 +02:00
float knobX = ((float)(*value_) - minValue_) / (maxValue_ - minValue_) * (bounds_.w - paddingLeft_ - paddingRight_) + (bounds_.x + paddingLeft_);
dc.FillRect(Drawable(linecolor), Bounds(bounds_.x + paddingLeft_, bounds_.centerY() - 2, knobX - (bounds_.x + paddingLeft_), 4));
2013-08-20 12:48:48 +02:00
dc.FillRect(Drawable(0xFF808080), Bounds(knobX, bounds_.centerY() - 2, (bounds_.x + bounds_.w - paddingRight_ - knobX), 4));
dc.Draw()->DrawImage(dc.theme->sliderKnob, knobX, bounds_.centerY(), 1.0f, knobcolor, ALIGN_CENTER);
2013-08-20 12:48:48 +02:00
char temp[64];
if (showPercent_)
sprintf(temp, "%i%%", *value_);
else
sprintf(temp, "%i", *value_);
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
2013-07-18 10:25:30 +02:00
}
void Slider::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
// TODO
w = 100;
h = 50;
}
2013-07-20 13:54:09 +02:00
void SliderFloat::Key(const KeyInput &input) {
if (HasFocus() && (input.flags & KEY_DOWN)) {
2013-07-20 13:54:09 +02:00
switch (input.keyCode) {
case NKCODE_DPAD_LEFT:
case NKCODE_MINUS:
case NKCODE_NUMPAD_SUBTRACT:
2013-08-17 12:06:08 +02:00
*value_ -= (maxValue_ - minValue_) / 20.0f;
2013-07-20 13:54:09 +02:00
break;
case NKCODE_DPAD_RIGHT:
case NKCODE_PLUS:
case NKCODE_NUMPAD_ADD:
*value_ += (maxValue_ - minValue_) / 30.0f;
break;
case NKCODE_PAGE_UP:
*value_ -= (maxValue_ - minValue_) / 5.0f;
break;
case NKCODE_PAGE_DOWN:
*value_ += (maxValue_ - minValue_) / 5.0f;
break;
case NKCODE_MOVE_HOME:
*value_ = minValue_;
break;
case NKCODE_MOVE_END:
*value_ = maxValue_;
2013-07-20 13:54:09 +02:00
break;
}
Clamp();
}
}
void SliderFloat::Touch(const TouchInput &input) {
if (dragging_ || bounds_.Contains(input.x, input.y)) {
2013-08-20 12:48:48 +02:00
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
*value_ = (relativeX * (maxValue_ - minValue_) + minValue_);
2013-07-20 13:54:09 +02:00
Clamp();
}
}
void SliderFloat::Clamp() {
if (*value_ < minValue_)
*value_ = minValue_;
else if (*value_ > maxValue_)
*value_ = maxValue_;
2013-07-20 13:54:09 +02:00
}
void SliderFloat::Draw(UIContext &dc) {
2013-08-20 12:48:48 +02:00
bool focus = HasFocus();
uint32_t linecolor = dc.theme->popupTitle.fgColor;
uint32_t knobcolor = (down_ || focus) ? dc.theme->popupTitle.fgColor : 0xFFFFFFFF;
2013-08-20 12:48:48 +02:00
float knobX = (*value_ - minValue_) / (maxValue_ - minValue_) * (bounds_.w - paddingLeft_ - paddingRight_) + (bounds_.x + paddingLeft_);
dc.FillRect(Drawable(linecolor), Bounds(bounds_.x + paddingLeft_, bounds_.centerY() - 2, knobX - (bounds_.x + paddingLeft_), 4));
2013-08-20 12:48:48 +02:00
dc.FillRect(Drawable(0xFF808080), Bounds(knobX, bounds_.centerY() - 2, (bounds_.x + bounds_.w - paddingRight_ - knobX), 4));
dc.Draw()->DrawImage(dc.theme->sliderKnob, knobX, bounds_.centerY(), 1.0f, knobcolor, ALIGN_CENTER);
2013-08-20 12:48:48 +02:00
char temp[64];
sprintf(temp, "%0.2f", *value_);
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
2013-07-20 13:54:09 +02:00
}
void SliderFloat::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
// TODO
w = 100;
h = 50;
}
2013-05-03 00:21:39 +02:00
} // namespace