mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-24 05:49:58 +00:00
NewUI: Add basic keyboard focus movement
This commit is contained in:
parent
8b75763275
commit
f482d3c857
43
ui/view.cpp
43
ui/view.cpp
@ -9,6 +9,7 @@
|
||||
namespace UI {
|
||||
|
||||
static View *focusedView;
|
||||
static bool focusMovementEnabled;
|
||||
|
||||
const float ITEM_HEIGHT = 48.f;
|
||||
|
||||
@ -20,6 +21,14 @@ void SetFocusedView(View *view) {
|
||||
focusedView = view;
|
||||
}
|
||||
|
||||
void EnableFocusMovement(bool enable) {
|
||||
focusMovementEnabled = true;
|
||||
}
|
||||
|
||||
bool IsFocusMovementEnabled() {
|
||||
return focusMovementEnabled;
|
||||
}
|
||||
|
||||
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured) {
|
||||
*measured = sz;
|
||||
if (sz == WRAP_CONTENT) {
|
||||
@ -64,7 +73,6 @@ void Event::Update() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void View::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
float contentW = 0.0f, contentH = 0.0f;
|
||||
GetContentDimensions(dc, contentW, contentH);
|
||||
@ -88,6 +96,7 @@ void Clickable::Click() {
|
||||
void Clickable::Touch(const TouchInput &input) {
|
||||
if (input.flags & (TOUCH_DOWN | TOUCH_MOVE)) {
|
||||
if (bounds_.Contains(input.x, input.y)) {
|
||||
if (IsFocusMovementEnabled())
|
||||
SetFocusedView(this);
|
||||
down_ = true;
|
||||
} else {
|
||||
@ -102,10 +111,18 @@ void Clickable::Touch(const TouchInput &input) {
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::GetContentDimensions(const DrawContext &dc, float &w, float &h) const {
|
||||
// Will be sized to fill parent horizontally.
|
||||
w = 0.0f;
|
||||
h = ITEM_HEIGHT;
|
||||
void Clickable::Update(const InputState &input_state) {
|
||||
if (!HasFocus())
|
||||
return;
|
||||
if (input_state.pad_buttons_down & PAD_BUTTON_A) {
|
||||
down_ = true;
|
||||
} else if (input_state.pad_buttons_up & PAD_BUTTON_A) {
|
||||
if (down_) {
|
||||
UI::EventParams e;
|
||||
OnClick.Trigger(e);
|
||||
}
|
||||
down_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::Draw(DrawContext &dc) {
|
||||
@ -115,6 +132,14 @@ void Choice::Draw(DrawContext &dc) {
|
||||
// dc.draw->DrawText(dc.theme->uiFontSmaller, text_.c_str(), paddingX, paddingY, 0xFFFFFFFF, ALIGN_TOPLEFT);
|
||||
}
|
||||
|
||||
void CheckBox::Draw(DrawContext &dc) {
|
||||
int paddingX = 80;
|
||||
int paddingY = 4;
|
||||
dc.draw->DrawImage(dc.theme->checkOn, bounds_.x + 30, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER);
|
||||
dc.draw->DrawText(dc.theme->uiFont, text_.c_str(), bounds_.x + paddingX, bounds_.y + paddingY, 0xFFFFFFFF, ALIGN_TOPLEFT);
|
||||
// dc.draw->DrawText(dc.theme->uiFontSmaller, text_.c_str(), paddingX, paddingY, 0xFFFFFFFF, ALIGN_TOPLEFT);
|
||||
}
|
||||
|
||||
void Button::GetContentDimensions(const DrawContext &dc, float &w, float &h) const {
|
||||
dc.draw->MeasureText(dc.theme->uiFont, text_.c_str(), &w, &h);
|
||||
}
|
||||
@ -122,8 +147,12 @@ void Button::GetContentDimensions(const DrawContext &dc, float &w, float &h) con
|
||||
void Button::Draw(DrawContext &dc) {
|
||||
int image = down_ ? dc.theme->buttonImage : dc.theme->buttonSelected;
|
||||
|
||||
dc.draw->DrawImage4Grid(dc.theme->buttonImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), style_.bgColor);
|
||||
dc.draw->DrawText(dc.theme->uiFont, text_.c_str(), bounds_.centerX(), bounds_.centerY(), style_.fgColor, ALIGN_CENTER);
|
||||
Style style = dc.theme->buttonStyle;
|
||||
if (HasFocus()) style = dc.theme->buttonFocusedStyle;
|
||||
if (down_) style = dc.theme->buttonDownStyle;
|
||||
|
||||
dc.draw->DrawImage4Grid(dc.theme->buttonImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), style.bgColor);
|
||||
dc.draw->DrawText(dc.theme->uiFont, text_.c_str(), bounds_.centerX(), bounds_.centerY(), style.fgColor, ALIGN_CENTER);
|
||||
}
|
||||
|
||||
void ImageView::GetContentDimensions(const DrawContext &dc, float &w, float &h) const {
|
||||
|
121
ui/view.h
121
ui/view.h
@ -16,6 +16,7 @@
|
||||
#include "math/lin/matrix4x4.h"
|
||||
|
||||
struct TouchInput;
|
||||
struct InputState;
|
||||
|
||||
class DrawBuffer;
|
||||
class DrawContext;
|
||||
@ -23,6 +24,15 @@ class DrawContext;
|
||||
// I don't generally like namespaces but I think we do need one for UI, so many potentially-clashing names.
|
||||
namespace UI {
|
||||
|
||||
class View;
|
||||
|
||||
// The ONLY global is the currently focused item.
|
||||
// Can be and often is null.
|
||||
void EnableFocusMovement(bool enable);
|
||||
bool IsFocusMovementEnabled();
|
||||
View *GetFocusedView();
|
||||
void SetFocusedView(View *view);
|
||||
|
||||
enum DrawableType {
|
||||
DRAW_NOTHING,
|
||||
DRAW_SOLID_COLOR,
|
||||
@ -52,7 +62,20 @@ struct Theme {
|
||||
int buttonSelected;
|
||||
int checkOn;
|
||||
int checkOff;
|
||||
|
||||
Style buttonStyle;
|
||||
Style buttonFocusedStyle;
|
||||
Style buttonDownStyle;
|
||||
};
|
||||
|
||||
// The four cardinal directions should be enough, plus Prev/Next in "element order".
|
||||
enum FocusDirection {
|
||||
FOCUS_UP,
|
||||
FOCUS_DOWN,
|
||||
FOCUS_LEFT,
|
||||
FOCUS_RIGHT,
|
||||
FOCUS_NEXT,
|
||||
FOCUS_PREV,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -118,10 +141,8 @@ struct Bounds {
|
||||
float h;
|
||||
};
|
||||
|
||||
|
||||
void Fill(DrawContext &dc, const Bounds &bounds, const Drawable &drawable);
|
||||
|
||||
|
||||
struct MeasureSpec {
|
||||
MeasureSpec(MeasureSpecType t, float s = 0.0f) : type(t), size(s) {}
|
||||
MeasureSpec() : type(UNSPECIFIED), size(0) {}
|
||||
@ -232,6 +253,7 @@ public:
|
||||
// Can even be called on a different thread! This is to really minimize latency, and decouple
|
||||
// touch response from the frame rate.
|
||||
virtual void Touch(const TouchInput &input) = 0;
|
||||
virtual void Update(const InputState &input_state) = 0;
|
||||
|
||||
void Move(Bounds bounds) {
|
||||
bounds_ = bounds;
|
||||
@ -253,9 +275,17 @@ public:
|
||||
virtual const LayoutParams *GetLayoutParams() const { return layoutParams_.get(); }
|
||||
const Bounds &GetBounds() const { return bounds_; }
|
||||
|
||||
virtual bool SetFocus() {
|
||||
if (CanBeFocused()) {
|
||||
SetFocusedView(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual void MoveFocus(FocusDirection direction) {}
|
||||
virtual bool CanBeFocused() const { return true; }
|
||||
|
||||
bool Focused() const {
|
||||
bool HasFocus() const {
|
||||
return GetFocusedView() == this;
|
||||
}
|
||||
|
||||
@ -283,6 +313,8 @@ public:
|
||||
: View(layoutParams) {}
|
||||
|
||||
virtual void Touch(const TouchInput &input) {}
|
||||
virtual bool CanBeFocused() const { return false; }
|
||||
virtual void Update(const InputState &input_state) {}
|
||||
};
|
||||
|
||||
|
||||
@ -290,9 +322,10 @@ public:
|
||||
class Clickable : public View {
|
||||
public:
|
||||
Clickable(LayoutParams *layoutParams)
|
||||
: View(layoutParams) {}
|
||||
: View(layoutParams), down_(false) {}
|
||||
|
||||
virtual void Touch(const TouchInput &input);
|
||||
virtual void Update(const InputState &input_state);
|
||||
|
||||
Event OnClick;
|
||||
|
||||
@ -319,38 +352,93 @@ private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Button);
|
||||
};
|
||||
|
||||
class Item : public InertView {
|
||||
public:
|
||||
Item(LayoutParams *layoutParams) : InertView(layoutParams) {
|
||||
layoutParams_->width = FILL_PARENT;
|
||||
layoutParams_->height = 80;
|
||||
}
|
||||
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const {
|
||||
w = 0.0f;
|
||||
h = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
class ClickableItem : public Clickable {
|
||||
public:
|
||||
ClickableItem(LayoutParams *layoutParams) : Clickable(layoutParams) {
|
||||
layoutParams_->width = FILL_PARENT;
|
||||
layoutParams_->height = 80;
|
||||
}
|
||||
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const {
|
||||
w = 0.0f;
|
||||
h = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// The following classes are mostly suitable as items in ListView which
|
||||
// really is just a LinearLayout in a ScrollView, possibly with some special optimizations.
|
||||
|
||||
// Use to trigger something or open a submenu screen.
|
||||
class Choice : public Clickable {
|
||||
class Choice : public ClickableItem {
|
||||
public:
|
||||
Choice(const std::string &text, const std::string &smallText = "", LayoutParams *layoutParams = 0)
|
||||
: Clickable(layoutParams), text_(text), smallText_(smallText) {}
|
||||
: ClickableItem(layoutParams), text_(text), smallText_(smallText) {}
|
||||
|
||||
virtual void Draw(DrawContext &dc);
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const;
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
std::string smallText_;
|
||||
};
|
||||
|
||||
class InfoItem : public Item {
|
||||
public:
|
||||
InfoItem(const std::string &text, const std::string &rightText, LayoutParams *layoutParams = 0)
|
||||
: Item(layoutParams), text_(text), rightText_(rightText) {}
|
||||
|
||||
virtual void Draw(DrawContext &dc);
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
std::string rightText_;
|
||||
};
|
||||
|
||||
class CheckBox : public Clickable {
|
||||
public:
|
||||
CheckBox(const std::string &text, const std::string &smallText = "", LayoutParams *layoutParams = 0)
|
||||
: Clickable(layoutParams), text_(text), smallText_(smallText) {}
|
||||
CheckBox(bool *toggle, const std::string &text, const std::string &smallText = "", LayoutParams *layoutParams = 0)
|
||||
: Clickable(layoutParams), text_(text), smallText_(smallText) {
|
||||
OnClick.Add(std::bind(&CheckBox::OnClicked, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const;
|
||||
virtual void Draw(DrawContext &dc);
|
||||
|
||||
EventReturn OnClicked(const EventParams &e) {
|
||||
if (toggle_)
|
||||
*toggle_ = !(*toggle_);
|
||||
return EVENT_DONE;
|
||||
}
|
||||
private:
|
||||
bool *toggle_;
|
||||
std::string text_;
|
||||
std::string smallText_;
|
||||
};
|
||||
|
||||
enum ImageSizeMode {
|
||||
|
||||
// These are for generic use.
|
||||
|
||||
class Spacer : public InertView {
|
||||
public:
|
||||
Spacer(LayoutParams *layoutParams = 0)
|
||||
: InertView(layoutParams) {}
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) {
|
||||
w = 0.0f; h = 0.0f;
|
||||
}
|
||||
virtual void Draw(DrawContext &dc) {}
|
||||
};
|
||||
|
||||
class TextView : public InertView {
|
||||
@ -360,13 +448,16 @@ public:
|
||||
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const;
|
||||
virtual void Draw(DrawContext &dc);
|
||||
virtual bool CanBeFocused() const { return false; }
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
int font_;
|
||||
};
|
||||
|
||||
enum ImageSizeMode {
|
||||
IS_DEFAULT,
|
||||
};
|
||||
|
||||
class ImageView : public InertView {
|
||||
public:
|
||||
ImageView(int atlasImage, ImageSizeMode sizeMode, LayoutParams *layoutParams = 0)
|
||||
@ -374,7 +465,6 @@ public:
|
||||
|
||||
virtual void GetContentDimensions(const DrawContext &dc, float &w, float &h) const;
|
||||
virtual void Draw(DrawContext &dc);
|
||||
virtual bool CanBeFocused() const { return false; }
|
||||
|
||||
private:
|
||||
int atlasImage_;
|
||||
@ -406,11 +496,6 @@ private:
|
||||
std::vector<Tab> tabs_;
|
||||
};*/
|
||||
|
||||
// The ONLY global is the currently focused item.
|
||||
// Can be and often is null.
|
||||
|
||||
View *GetFocusedView();
|
||||
void SetFocusedView(View *view);
|
||||
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured);
|
||||
|
||||
} // namespace
|
@ -42,6 +42,39 @@ void ViewGroup::Draw(DrawContext &dc) {
|
||||
}
|
||||
}
|
||||
|
||||
void ViewGroup::Update(const InputState &input_state) {
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
||||
(*iter)->Update(input_state);
|
||||
}
|
||||
}
|
||||
|
||||
bool ViewGroup::SetFocus() {
|
||||
if (!CanBeFocused() && !views_.empty()) {
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
if (views_[i]->SetFocus())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewGroup::MoveFocus(FocusDirection direction) {
|
||||
if (!GetFocusedView()) {
|
||||
SetFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
View *neighbor = FindNeighbor(GetFocusedView(), direction);
|
||||
if (neighbor) {
|
||||
neighbor->SetFocus();
|
||||
} else {
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
(*iter)->MoveFocus(direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
View *ViewGroup::FindNeighbor(View *view, FocusDirection direction) {
|
||||
// First, find the position of the view in the list.
|
||||
size_t num = -1;
|
||||
@ -74,15 +107,9 @@ View *ViewGroup::FindNeighbor(View *view, FocusDirection direction) {
|
||||
}
|
||||
}
|
||||
|
||||
void FrameLayout::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
if (views_.empty()) {
|
||||
ELOG("A FrameLayout must have a child view");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void LinearLayout::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
if (!views_.size()) {
|
||||
if (views_.empty()) {
|
||||
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
||||
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
||||
return;
|
||||
@ -215,8 +242,26 @@ void LinearLayout::Layout() {
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollView::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
void FrameLayout::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
if (views_.empty()) {
|
||||
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
||||
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
views_[i]->Measure(dc, horiz, vert);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameLayout::Layout() {
|
||||
|
||||
}
|
||||
|
||||
void ScrollView::Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
// The scroll view itself simply obeys its parent.
|
||||
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
||||
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
||||
}
|
||||
|
||||
void ScrollView::Layout() {
|
||||
@ -246,6 +291,7 @@ void ScrollView::Touch(const TouchInput &input) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GridLayout::Layout() {
|
||||
|
||||
}
|
||||
@ -271,4 +317,22 @@ void LayoutViewHierarchy(const DrawContext &dc, ViewGroup *root) {
|
||||
root->Layout();
|
||||
}
|
||||
|
||||
void UpdateViewHierarchy(const InputState &input_state, ViewGroup *root) {
|
||||
if (input_state.pad_buttons_down & (PAD_BUTTON_LEFT | PAD_BUTTON_RIGHT | PAD_BUTTON_UP | PAD_BUTTON_DOWN))
|
||||
EnableFocusMovement(true);
|
||||
|
||||
if (input_state.pad_last_buttons == 0) {
|
||||
if (input_state.pad_buttons_down & PAD_BUTTON_RIGHT)
|
||||
root->MoveFocus(FOCUS_RIGHT);
|
||||
if (input_state.pad_buttons_down & PAD_BUTTON_UP)
|
||||
root->MoveFocus(FOCUS_UP);
|
||||
if (input_state.pad_buttons_down & PAD_BUTTON_LEFT)
|
||||
root->MoveFocus(FOCUS_LEFT);
|
||||
if (input_state.pad_buttons_down & PAD_BUTTON_DOWN)
|
||||
root->MoveFocus(FOCUS_DOWN);
|
||||
}
|
||||
|
||||
root->Update(input_state);
|
||||
}
|
||||
|
||||
} // namespace UI
|
@ -5,16 +5,6 @@
|
||||
|
||||
namespace UI {
|
||||
|
||||
// The four cardinal directions should be enough, plus Prev/Next in "element order".
|
||||
enum FocusDirection {
|
||||
FOCUS_UP,
|
||||
FOCUS_DOWN,
|
||||
FOCUS_LEFT,
|
||||
FOCUS_RIGHT,
|
||||
FOCUS_NEXT,
|
||||
FOCUS_PREV,
|
||||
};
|
||||
|
||||
class ViewGroup : public View {
|
||||
public:
|
||||
ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams) {}
|
||||
@ -26,6 +16,7 @@ public:
|
||||
// By default, a container will layout to its own bounds.
|
||||
virtual void Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert) = 0;
|
||||
virtual void Layout() = 0;
|
||||
virtual void Update(const InputState &input_state);
|
||||
|
||||
virtual void Draw(DrawContext &dc);
|
||||
|
||||
@ -36,9 +27,14 @@ public:
|
||||
// Takes ownership! DO NOT add a view to multiple parents!
|
||||
void Add(View *view) { views_.push_back(view); }
|
||||
|
||||
virtual bool SetFocus();
|
||||
virtual void MoveFocus(FocusDirection direction);
|
||||
|
||||
// Assumes that layout has taken place.
|
||||
View *FindNeighbor(View *view, FocusDirection direction);
|
||||
|
||||
virtual bool CanBeFocused() const { return false; }
|
||||
|
||||
protected:
|
||||
std::vector<View *> views_;
|
||||
};
|
||||
@ -72,15 +68,15 @@ private:
|
||||
|
||||
class GridLayout : public ViewGroup {
|
||||
public:
|
||||
GridLayout(Orientation orientation, int colsOrRows) :
|
||||
orientation_(orientation), colsOrRows_(colsOrRows) {}
|
||||
GridLayout(Orientation orientation, int numPerLine) :
|
||||
orientation_(orientation), numPerLine_(numPerLine) {}
|
||||
|
||||
void Measure(const DrawContext &dc, MeasureSpec horiz, MeasureSpec vert);
|
||||
void Layout();
|
||||
|
||||
private:
|
||||
Orientation orientation_;
|
||||
int colsOrRows_;
|
||||
int numPerLine_;
|
||||
};
|
||||
|
||||
// A scrollview usually contains just a single child - a linear layout or similar.
|
||||
@ -106,5 +102,6 @@ public:
|
||||
};
|
||||
|
||||
void LayoutViewHierarchy(const DrawContext &dc, ViewGroup *root);
|
||||
void UpdateViewHierarchy(const InputState &input_state, ViewGroup *root);
|
||||
|
||||
} // namespace UI
|
Loading…
Reference in New Issue
Block a user