diff options
Diffstat (limited to 'Samples/CommonSrc/Util')
-rw-r--r-- | Samples/CommonSrc/Util/OptionMenu.cpp | 899 | ||||
-rw-r--r-- | Samples/CommonSrc/Util/OptionMenu.h | 445 | ||||
-rw-r--r-- | Samples/CommonSrc/Util/RenderProfiler.cpp | 99 | ||||
-rw-r--r-- | Samples/CommonSrc/Util/RenderProfiler.h | 71 |
4 files changed, 1514 insertions, 0 deletions
diff --git a/Samples/CommonSrc/Util/OptionMenu.cpp b/Samples/CommonSrc/Util/OptionMenu.cpp new file mode 100644 index 0000000..d9acc44 --- /dev/null +++ b/Samples/CommonSrc/Util/OptionMenu.cpp @@ -0,0 +1,899 @@ +/************************************************************************************ + +Filename : OptionMenu.h +Content : Option selection and editing for OculusWorldDemo +Created : March 7, 2014 +Authors : Michael Antonov, Caleb Leak + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*************************************************************************************/ + +#include "OptionMenu.h" + +// Embed the font. +#include "../Render/Render_FontEmbed_DejaVu48.h" + + +//------------------------------------------------------------------------------------- +bool OptionShortcut::MatchKey(OVR::KeyCode key, bool shift) const +{ + for (UInt32 i = 0; i < Keys.GetSize(); i++) + { + if (Keys[i].Key != key) + continue; + + if (!shift && Keys[i].ShiftUsage == ShortcutKey::Shift_RequireOn) + continue; + + if (shift && Keys[i].ShiftUsage == ShortcutKey::Shift_RequireOff) + continue; + + if(Keys[i].ShiftUsage == ShortcutKey::Shift_Modify) + { + pNotify->CallNotify(&shift); + } + else + { + pNotify->CallNotify(); + } + return true; + } + return false; +} + +bool OptionShortcut::MatchGamepadButton(UInt32 gamepadButtonMask) const +{ + for (UInt32 i = 0; i < GamepadButtons.GetSize(); i++) + { + if (GamepadButtons[i] & gamepadButtonMask) + { + if (pNotify != NULL) + { + pNotify->CallNotify(); + } + return true; + } + } + return false; +} + + +//------------------------------------------------------------------------------------- +String OptionMenuItem::PopNamespaceFrom(OptionMenuItem* menuItem) +{ + String label = menuItem->Label; + for (UInt32 i = 0; i < label.GetLength(); i++) + { + if (label.GetCharAt(i) == '.') + { + String ns = label.Substring(0, i); + menuItem->Label = label.Substring(i + 1, label.GetLength()); + return ns; + } + } + return ""; +} + +//------------------------------------------------------------------------------------- + +String OptionVar::FormatEnum(OptionVar* var) +{ + UInt32 index = var->GetEnumIndex(); + if (index < var->EnumValues.GetSize()) + return var->EnumValues[index].Name; + return String("<Bad enum index>"); +} + +String OptionVar::FormatInt(OptionVar* var) +{ + char buff[64]; + OVR_sprintf(buff, sizeof(buff), var->FormatString, *var->AsInt()); + return String(buff); +} + +String OptionVar::FormatFloat(OptionVar* var) +{ + char buff[64]; + OVR_sprintf(buff, sizeof(buff), var->FormatString, *var->AsFloat() * var->FormatScale); + return String(buff); +} + +String OptionVar::FormatBool(OptionVar* var) +{ + return *var->AsBool() ? "On" : "Off"; +} + + +OptionVar::OptionVar(const char* name, void* pvar, VarType type, + FormatFunction formatFunction, + UpdateFunction updateFunction) +{ + Label = name; + Type = type; + this->pVar = pvar; + fFormat = formatFunction; + fUpdate = updateFunction; + pNotify = 0; + FormatString= 0; + + MaxFloat = Math<float>::MaxValue; + MinFloat = -Math<float>::MaxValue; + StepFloat = 1.0f; + FormatScale = 1.0f; + + MaxInt = 0x7FFFFFFF; + MinInt = -(MaxInt) - 1; + StepInt = 1; + + ShortcutUp.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::NextValue); + ShortcutDown.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::PrevValue); +} + +OptionVar::OptionVar(const char* name, SInt32* pvar, + SInt32 min, SInt32 max, SInt32 stepSize, + const char* formatString, + FormatFunction formatFunction, + UpdateFunction updateFunction) +{ + Label = name; + Type = Type_Int; + this->pVar = pvar; + fFormat = formatFunction ? formatFunction : FormatInt; + fUpdate = updateFunction; + pNotify = 0; + FormatString= formatString; + + MinInt = min; + MaxInt = max; + StepInt = stepSize; + + ShortcutUp.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::NextValue); + ShortcutDown.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::PrevValue); +} + +// Float with range and step size. +OptionVar::OptionVar(const char* name, float* pvar, + float minf, float maxf, float stepSize, + const char* formatString, float formatScale, + FormatFunction formatFunction, + UpdateFunction updateFunction) +{ + Label = name; + Type = Type_Float; + this->pVar = pvar; + fFormat = formatFunction ? formatFunction : FormatFloat; + fUpdate = updateFunction; + pNotify = 0; + FormatString= formatString ? formatString : "%.3f"; + + MinFloat = minf; + MaxFloat = maxf; + StepFloat = stepSize; + FormatScale = formatScale; + + ShortcutUp.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::NextValue); + ShortcutDown.pNotify = new FunctionNotifyContext<OptionVar, bool>(this, &OptionVar::PrevValue); +} + +OptionVar::~OptionVar() +{ + if (pNotify) + delete pNotify; +} + +void OptionVar::NextValue(bool* pFastStep) +{ + bool fastStep = (pFastStep != NULL && *pFastStep); + switch (Type) + { + case Type_Enum: + *AsInt() = ((GetEnumIndex() + 1) % EnumValues.GetSize()); + break; + + case Type_Int: + *AsInt() = Alg::Min<SInt32>(*AsInt() + StepInt * (fastStep ? 5 : 1), MaxInt); + break; + + case Type_Float: + // TODO: Will behave strange with NaN values. + *AsFloat() = Alg::Min<float>(*AsFloat() + StepFloat * (fastStep ? 5.0f : 1.0f), MaxFloat); + break; + + case Type_Bool: + *AsBool() = !*AsBool(); + break; + } + + SignalUpdate(); +} + +void OptionVar::PrevValue(bool* pFastStep) +{ + bool fastStep = (pFastStep != NULL && *pFastStep); + switch (Type) + { + case Type_Enum: + *AsInt() = ((GetEnumIndex() + (UInt32)EnumValues.GetSize() - 1) % EnumValues.GetSize()); + break; + + case Type_Int: + *AsInt() = Alg::Max<SInt32>(*AsInt() - StepInt * (fastStep ? 5 : 1), MinInt); + break; + + case Type_Float: + // TODO: Will behave strange with NaN values. + *AsFloat() = Alg::Max<float>(*AsFloat() - StepFloat * (fastStep ? 5.0f : 1.0f), MinFloat); + break; + + case Type_Bool: + *AsBool() = !*AsBool(); + break; + } + + SignalUpdate(); +} + +String OptionVar::HandleShortcutUpdate() +{ + SignalUpdate(); + return Label + " - " + GetValue(); +} + +String OptionVar::ProcessShortcutKey(OVR::KeyCode key, bool shift) +{ + if (ShortcutUp.MatchKey(key, shift) || ShortcutDown.MatchKey(key, shift)) + { + return HandleShortcutUpdate(); + } + + return String(); +} + +String OptionVar::ProcessShortcutButton(UInt32 buttonMask) +{ + if (ShortcutUp.MatchGamepadButton(buttonMask) || ShortcutDown.MatchGamepadButton(buttonMask)) + { + return HandleShortcutUpdate(); + } + return String(); +} + + +OptionVar& OptionVar::AddEnumValue(const char* displayName, SInt32 value) +{ + EnumEntry entry; + entry.Name = displayName; + entry.Value = value; + EnumValues.PushBack(entry); + return *this; +} + +String OptionVar::GetValue() +{ + return fFormat(this); +} + +UInt32 OptionVar::GetEnumIndex() +{ + OVR_ASSERT(Type == Type_Enum); + OVR_ASSERT(EnumValues.GetSize() > 0); + + // TODO: Change this from a linear search to binary or a hash. + for (UInt32 i = 0; i < EnumValues.GetSize(); i++) + { + if (EnumValues[i].Value == *AsInt()) + return i; + } + + // Enum values should always be found. + OVR_ASSERT(false); + return 0; +} + + +//------------------------------------------------------------------------------------- + +OptionSelectionMenu::OptionSelectionMenu(OptionSelectionMenu* parentMenu) +{ + DisplayState = Display_None; + SelectedIndex = 0; + SelectionActive = false; + ParentMenu = parentMenu; + + PopupMessageTimeout = 0.0; + PopupMessageBorder = false; + + // Setup handlers for menu navigation actions. + NavShortcuts[Nav_Up].pNotify = new FunctionNotifyContext<OptionSelectionMenu, bool>(this, &OptionSelectionMenu::HandleUp); + NavShortcuts[Nav_Down].pNotify = new FunctionNotifyContext<OptionSelectionMenu, bool>(this, &OptionSelectionMenu::HandleDown); + NavShortcuts[Nav_Left].pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleLeft); + NavShortcuts[Nav_Right].pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleRight); + NavShortcuts[Nav_Select].pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleSelect); + NavShortcuts[Nav_Back].pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleBack); + ToggleShortcut.pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleMenuToggle); + ToggleSingleItemShortcut.pNotify = new FunctionNotifySimple<OptionSelectionMenu>(this, &OptionSelectionMenu::HandleSingleItemToggle); + + // Bind keys and buttons to menu navigation actions. + NavShortcuts[Nav_Up].AddShortcut(ShortcutKey(Key_Up, ShortcutKey::Shift_Modify)); + NavShortcuts[Nav_Up].AddShortcut(Gamepad_Up); + + NavShortcuts[Nav_Down].AddShortcut(ShortcutKey(Key_Down, ShortcutKey::Shift_Modify)); + NavShortcuts[Nav_Down].AddShortcut(Gamepad_Down); + + NavShortcuts[Nav_Left].AddShortcut(ShortcutKey(Key_Left)); + NavShortcuts[Nav_Left].AddShortcut(Gamepad_Left); + + NavShortcuts[Nav_Right].AddShortcut(ShortcutKey(Key_Right)); + NavShortcuts[Nav_Right].AddShortcut(Gamepad_Right); + + NavShortcuts[Nav_Select].AddShortcut(ShortcutKey(Key_Return)); + NavShortcuts[Nav_Select].AddShortcut(Gamepad_A); + + NavShortcuts[Nav_Back].AddShortcut(ShortcutKey(Key_Escape)); + NavShortcuts[Nav_Back].AddShortcut(Gamepad_B); + + ToggleShortcut.AddShortcut(ShortcutKey(Key_Tab, ShortcutKey::Shift_Ignore)); + ToggleShortcut.AddShortcut(Gamepad_Start); + + ToggleSingleItemShortcut.AddShortcut(ShortcutKey(Key_Backspace, ShortcutKey::Shift_Ignore)); +} + +OptionSelectionMenu::~OptionSelectionMenu() +{ + for (UInt32 i = 0; i < Items.GetSize(); i++) + delete Items[i]; +} + +bool OptionSelectionMenu::OnKey(OVR::KeyCode key, int chr, bool down, int modifiers) +{ + bool shift = ((modifiers & Mod_Shift) != 0); + + if (down) + { + String s = ProcessShortcutKey(key, shift); + if (!s.IsEmpty()) + { + PopupMessage = s; + PopupMessageTimeout = ovr_GetTimeInSeconds() + 4.0f; + PopupMessageBorder = false; + return true; + } + } + + if (GetSubmenu() != NULL) + { + return GetSubmenu()->OnKey(key, chr, down, modifiers); + } + + if (down) + { + if (ToggleShortcut.MatchKey(key, shift)) + return true; + + if (ToggleSingleItemShortcut.MatchKey(key, shift)) + return true; + + if (DisplayState == Display_None) + return false; + + for (int i = 0; i < Nav_LAST; i++) + { + if (NavShortcuts[i].MatchKey(key, shift)) + return true; + } + } + + // Let the caller process keystroke + return false; +} + +bool OptionSelectionMenu::OnGamepad(UInt32 buttonMask) +{ + // Check global shortcuts first. + String s = ProcessShortcutButton(buttonMask); + if (!s.IsEmpty()) + { + PopupMessage = s; + PopupMessageTimeout = ovr_GetTimeInSeconds() + 4.0f; + return true; + } + + if (GetSubmenu() != NULL) + { + return GetSubmenu()->OnGamepad(buttonMask); + } + + if (ToggleShortcut.MatchGamepadButton(buttonMask)) + return true; + + if (DisplayState == Display_None) + return false; + + for (int i = 0; i < Nav_LAST; i++) + { + if (NavShortcuts[i].MatchGamepadButton(buttonMask)) + return true; + } + + // Let the caller process keystroke + return false; +} + +String OptionSelectionMenu::ProcessShortcutKey(OVR::KeyCode key, bool shift) +{ + String s; + + for (UPInt i = 0; (i < Items.GetSize()) && s.IsEmpty(); i++) + { + s = Items[i]->ProcessShortcutKey(key, shift); + } + + return s; +} + +String OptionSelectionMenu::ProcessShortcutButton(UInt32 buttonMask) +{ + String s; + + for (UPInt i = 0; (i < Items.GetSize()) && s.IsEmpty(); i++) + { + s = Items[i]->ProcessShortcutButton(buttonMask); + } + + return s; +} + +// Fills in inclusive character range; returns false if line not found. +bool FindLineCharRange(const char* text, int searchLine, UPInt charRange[2]) +{ + UPInt i = 0; + + for (int line = 0; line <= searchLine; line ++) + { + if (line == searchLine) + { + charRange[0] = i; + } + + // Find end of line. + while (text[i] != '\n' && text[i] != 0) + { + i++; + } + + if (line == searchLine) + { + charRange[1] = (charRange[0] == i) ? charRange[0] : i-1; + return true; + } + + if (text[i] == 0) + break; + // Skip newline + i++; + } + + return false; +} + + +void OptionSelectionMenu::Render(RenderDevice* prender, String title) +{ + // If we are invisible, render shortcut notifications. + // Both child and parent have visible == true even if only child is shown. + if (DisplayState == Display_None) + { + renderShortcutChangeMessage(prender); + return; + } + + title += Label; + + // Delegate to sub-menu if active. + if (GetSubmenu() != NULL) + { + if (title.GetSize() > 0) + title += " > "; + + GetSubmenu()->Render(prender, title); + return; + } + + Color focusColor(180, 80, 20, 210); + Color pickedColor(120, 55, 10, 140); + Color titleColor(0x18, 0x1A, 0x4D, 210); + Color titleOutlineColor(0x18, 0x18, 0x18, 240); + + float labelsSize[2] = {0.0f, 0.0f}; + float bufferSize[2] = {0.0f, 0.0f}; + float valuesSize[2] = {0.0f, 0.0f}; + float maxValueWidth = 0.0f; + + UPInt selection[2] = { 0, 0 }; + Vector2f labelSelectionRect[2]; + Vector2f valueSelectionRect[2]; + bool havelLabelSelection = false; + bool haveValueSelection = false; + + float textSize = 22.0f; + prender->MeasureText(&DejaVu, " ", textSize, bufferSize); + + String values; + String menuItems; + + int highlightIndex = 0; + if (DisplayState == Display_Menu) + { + highlightIndex = SelectedIndex; + for (UInt32 i = 0; i < Items.GetSize(); i++) + { + if (i > 0) + values += "\n"; + values += Items[i]->GetValue(); + } + + for (UInt32 i = 0; i < Items.GetSize(); i++) + { + if (i > 0) + menuItems += "\n"; + menuItems += Items[i]->GetLabel(); + } + } + else + { + values = Items[SelectedIndex]->GetValue(); + menuItems = Items[SelectedIndex]->GetLabel(); + } + + // Measure labels + const char* menuItemsCStr = menuItems.ToCStr(); + havelLabelSelection = FindLineCharRange(menuItemsCStr, highlightIndex, selection); + prender->MeasureText(&DejaVu, menuItemsCStr, textSize, labelsSize, + selection, labelSelectionRect); + + // Measure label-to-value gap + const char* valuesCStr = values.ToCStr(); + haveValueSelection = FindLineCharRange(valuesCStr, highlightIndex, selection); + prender->MeasureText(&DejaVu, valuesCStr, textSize, valuesSize, selection, valueSelectionRect); + + // Measure max value size (absolute size varies, so just use a reasonable max) + maxValueWidth = prender->MeasureText(&DejaVu, "Max value width", textSize); + maxValueWidth = Alg::Max(maxValueWidth, valuesSize[0]); + + Vector2f borderSize(4.0f, 4.0f); + Vector2f totalDimensions = borderSize * 2 + Vector2f(bufferSize[0], 0) + Vector2f(maxValueWidth, 0) + + Vector2f(labelsSize[0], labelsSize[1]); + + Vector2f fudgeOffset= Vector2f(10.0f, 25.0f); // This offset looks better + Vector2f topLeft = (-totalDimensions / 2.0f) + fudgeOffset; + Vector2f bottomRight = topLeft + totalDimensions; + + // If displaying a single item, shift it down. + if (DisplayState == Display_SingleItem) + { + topLeft.y += textSize * 7; + bottomRight.y += textSize * 7; + } + + prender->FillRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y, Color(40,40,100,210)); + + Vector2f labelsPos = topLeft + borderSize; + Vector2f valuesPos = labelsPos + Vector2f(labelsSize[0], 0) + Vector2f(bufferSize[0], 0); + + // Highlight selected label + Vector2f selectionInset = Vector2f(0.3f, 2.0f); + if (DisplayState == Display_Menu) + { + Vector2f labelSelectionTopLeft = labelsPos + labelSelectionRect[0] - selectionInset; + Vector2f labelSelectionBottomRight = labelsPos + labelSelectionRect[1] + selectionInset; + + prender->FillRect(labelSelectionTopLeft.x, labelSelectionTopLeft.y, + labelSelectionBottomRight.x, labelSelectionBottomRight.y, + SelectionActive ? pickedColor : focusColor); + } + + // Highlight selected value if active + if (SelectionActive) + { + Vector2f valueSelectionTopLeft = valuesPos + valueSelectionRect[0] - selectionInset; + Vector2f valueSelectionBottomRight = valuesPos + valueSelectionRect[1] + selectionInset; + prender->FillRect(valueSelectionTopLeft.x, valueSelectionTopLeft.y, + valueSelectionBottomRight.x, valueSelectionBottomRight.y, + focusColor); + } + + // Measure and draw title + if (DisplayState == Display_Menu && title.GetLength() > 0) + { + Vector2f titleDimensions; + prender->MeasureText(&DejaVu, title.ToCStr(), textSize, &titleDimensions.x); + Vector2f titleTopLeft = topLeft - Vector2f(0, borderSize.y) * 2 - Vector2f(0, titleDimensions.y); + titleDimensions.x = totalDimensions.x; + + prender->FillRect(titleTopLeft.x, titleTopLeft.y, + titleTopLeft.x + totalDimensions.x, + titleTopLeft.y + titleDimensions.y + borderSize.y * 2, + titleOutlineColor); + + prender->FillRect(titleTopLeft.x + borderSize.x / 2, titleTopLeft.y + borderSize.y / 2, + titleTopLeft.x + totalDimensions.x - borderSize.x / 2, + titleTopLeft.y + borderSize.y / 2 + titleDimensions.y, + titleColor); + + prender->RenderText(&DejaVu, title.ToCStr(), titleTopLeft.x + borderSize.x, + titleTopLeft.y + borderSize.y, textSize, Color(255,255,0,210)); + } + + prender->RenderText(&DejaVu, menuItemsCStr, labelsPos.x, labelsPos.y, textSize, Color(255,255,0,210)); + + prender->RenderText(&DejaVu, valuesCStr, valuesPos.x, valuesPos.y, textSize, Color(255,255,0,210)); +} + + +void OptionSelectionMenu::renderShortcutChangeMessage(RenderDevice* prender) +{ + if (ovr_GetTimeInSeconds() < PopupMessageTimeout) + { + DrawTextBox(prender, 0, 120, 22.0f, PopupMessage.ToCStr(), + DrawText_Center | (PopupMessageBorder ? DrawText_Border : 0)); + } +} + + +void OptionSelectionMenu::SetPopupMessage(const char* format, ...) +{ + //Lock::Locker lock(pManager->GetHandlerLock()); + char textBuff[2048]; + va_list argList; + va_start(argList, format); + OVR_vsprintf(textBuff, sizeof(textBuff), format, argList); + va_end(argList); + + // Message will time out in 4 seconds. + PopupMessage = textBuff; + PopupMessageTimeout = ovr_GetTimeInSeconds() + 4.0f; + PopupMessageBorder = false; +} + +void OptionSelectionMenu::SetPopupTimeout(double timeoutSeconds, bool border) +{ + PopupMessageTimeout = ovr_GetTimeInSeconds() + timeoutSeconds; + PopupMessageBorder = border; +} + + + +void OptionSelectionMenu::AddItem(OptionMenuItem* menuItem) +{ + String ns = PopNamespaceFrom(menuItem); + + if (ns.GetLength() == 0) + { + Items.PushBack(menuItem); + } + else + { + // Item is part of a submenu, add it to that instead. + GetOrCreateSubmenu(ns)->AddItem(menuItem); + } +} + +//virtual +void OptionSelectionMenu::Select() +{ + SelectedIndex = 0; + SelectionActive = false; + DisplayState = Display_Menu; +} + + +OptionSelectionMenu* OptionSelectionMenu::GetSubmenu() +{ + if (!SelectionActive || !Items[SelectedIndex]->IsMenu()) + return NULL; + + OptionSelectionMenu* submenu = static_cast<OptionSelectionMenu*>(Items[SelectedIndex]); + return submenu; +} + + +OptionSelectionMenu* OptionSelectionMenu::GetOrCreateSubmenu(String submenuName) +{ + for (UInt32 i = 0; i < Items.GetSize(); i++) + { + if (!Items[i]->IsMenu()) + continue; + + OptionSelectionMenu* submenu = static_cast<OptionSelectionMenu*>(Items[i]); + + if (submenu->Label == submenuName) + { + return submenu; + } + } + + // Submenu doesn't exist, create it. + OptionSelectionMenu* newSubmenu = new OptionSelectionMenu(this); + newSubmenu->Label = submenuName; + Items.PushBack(newSubmenu); + return newSubmenu; +} + +void OptionSelectionMenu::HandleUp(bool* pFast) +{ + int numItems = (int)Items.GetSize(); + if (SelectionActive) + Items[SelectedIndex]->NextValue(pFast); + else + SelectedIndex = ((SelectedIndex - 1 + numItems) % numItems); +} + +void OptionSelectionMenu::HandleDown(bool* pFast) +{ + if (SelectionActive) + Items[SelectedIndex]->PrevValue(pFast); + else + SelectedIndex = ((SelectedIndex + 1) % Items.GetSize()); +} + +void OptionSelectionMenu::HandleLeft() +{ + if (DisplayState != Display_Menu) + return; + + if (SelectionActive) + SelectionActive = false; + else if (ParentMenu) + { + // Escape to parent menu + ParentMenu->SelectionActive = false; + DisplayState = Display_Menu; + } +} + +void OptionSelectionMenu::HandleRight() +{ + if (DisplayState != Display_Menu) + return; + + if (!SelectionActive) + { + SelectionActive = true; + Items[SelectedIndex]->Select(); + } +} + +void OptionSelectionMenu::HandleSelect() +{ + if (!SelectionActive) + { + SelectionActive = true; + Items[SelectedIndex]->Select(); + } + else + { + Items[SelectedIndex]->NextValue(); + } +} + +void OptionSelectionMenu::HandleBack() +{ + if (DisplayState != Display_Menu) + return; + + if (!SelectionActive) + DisplayState = Display_None; + else + SelectionActive = false; +} + +void OptionSelectionMenu::HandleMenuToggle() +{ + // Mark this & parent With correct visibility. + OptionSelectionMenu* menu = this; + + if (DisplayState == Display_Menu) + DisplayState = Display_None; + else + DisplayState = Display_Menu; + + while (menu) + { + menu->DisplayState = DisplayState; + menu = menu->ParentMenu; + } + // Hide message + PopupMessageTimeout = 0; +} + +void OptionSelectionMenu::HandleSingleItemToggle() +{ + // Mark this & parent With correct visibility. + OptionSelectionMenu* menu = this; + + if (DisplayState == Display_SingleItem) + DisplayState = Display_None; + else + { + DisplayState = Display_SingleItem; + SelectionActive = true; + } + + while (menu) + { + menu->DisplayState = DisplayState; + menu = menu->ParentMenu; + } + // Hide message + PopupMessageTimeout = 0; +} + + +//------------------------------------------------------------------------------------- +// **** Text Rendering / Management + +void DrawTextBox(RenderDevice* prender, float x, float y, + float textSize, const char* text, unsigned centerType) +{ + float ssize[2] = {0.0f, 0.0f}; + + prender->MeasureText(&DejaVu, text, textSize, ssize); + + // Treat 0 a VCenter. + if (centerType & DrawText_HCenter) + { + x -= ssize[0]/2; + } + if (centerType & DrawText_VCenter) + { + y -= ssize[1]/2; + } + + const float borderSize = 4.0f; + float linesHeight = 0.0f; + + if (centerType & DrawText_Border) + linesHeight = 10.0f; + + prender->FillRect(x-borderSize, y-borderSize - linesHeight, + x+ssize[0]+borderSize, y+ssize[1]+borderSize + linesHeight, + Color(40,40,100,210)); + + if (centerType & DrawText_Border) + { + // Add top & bottom lines + float topLineY = y-borderSize - linesHeight * 0.5f, + bottomLineY = y+ssize[1]+borderSize + linesHeight * 0.5f; + + prender->FillRect(x-borderSize * 0.5f, topLineY, + x+ssize[0]+borderSize * 0.5f, topLineY + 2.0f, + Color(255,255,0,210)); + prender->FillRect(x-borderSize * 0.5f, bottomLineY, + x+ssize[0]+borderSize * 0.5f, bottomLineY + 2.0f, + Color(255,255,0,210)); + } + + prender->RenderText(&DejaVu, text, x, y, textSize, Color(255,255,0,210)); +} + +void CleanupDrawTextFont() +{ + if (DejaVu.fill) + { + DejaVu.fill->Release(); + DejaVu.fill = 0; + } +} diff --git a/Samples/CommonSrc/Util/OptionMenu.h b/Samples/CommonSrc/Util/OptionMenu.h new file mode 100644 index 0000000..d52ba5c --- /dev/null +++ b/Samples/CommonSrc/Util/OptionMenu.h @@ -0,0 +1,445 @@ +/************************************************************************************ + +Filename : OptionMenu.h +Content : Option selection and editing for OculusWorldDemo +Created : March 7, 2014 +Authors : Michael Antonov, Caleb Leak + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*************************************************************************************/ + +#ifndef INC_OptionMenu_h +#define INC_OptionMenu_h + +#include "OVR.h" + +#include "../Platform/Platform_Default.h" +#include "../Render/Render_Device.h" +#include "../Platform/Gamepad.h" + +#include "Util/Util_Render_Stereo.h" +using namespace OVR::Util::Render; + +#include <Kernel/OVR_SysFile.h> +#include <Kernel/OVR_Log.h> +#include <Kernel/OVR_Timer.h> + +#include "OVR_DeviceConstants.h" + + +using namespace OVR; +using namespace OVR::Platform; +using namespace OVR::Render; + + +//------------------------------------------------------------------------------------- +struct FunctionNotifyBase : public NewOverrideBase +{ + virtual void CallNotify(void*) { } + virtual void CallNotify() { } +}; + +// Simple member pointer wrapper to support calling class members +template<class C, class X> +struct FunctionNotifyContext : public FunctionNotifyBase +{ + typedef void (C::*FnPtr)(X*); + + FunctionNotifyContext(C* p, FnPtr fn) : pClass(p), pFn(fn), pContext(NULL) { } + FunctionNotifyContext(C* p, FnPtr fn, X* pContext) : pClass(p), pFn(fn), pContext(pContext) { } + virtual void CallNotify(void* var) { (void)(pClass->*pFn)(static_cast<X*>(var)); } + virtual void CallNotify() { (void)(pClass->*pFn)(pContext); } +private: + + X* pContext; + C* pClass; + FnPtr pFn; +}; + +template<class C> +struct FunctionNotifySimple : public FunctionNotifyBase +{ + typedef void (C::*FnPtr)(void); + + FunctionNotifySimple(C* p, FnPtr fn) : pClass(p), pFn(fn) { } + virtual void CallNotify(void*) { CallNotify(); } + virtual void CallNotify() { (void)(pClass->*pFn)(); } +private: + + C* pClass; + FnPtr pFn; +}; + + +//------------------------------------------------------------------------------------- +// Describes a shortcut key +struct ShortcutKey +{ + enum ShiftUsageType + { + Shift_Ignore, + Shift_Modify, + Shift_RequireOn, + Shift_RequireOff + }; + + ShortcutKey(OVR::KeyCode key = Key_None, ShiftUsageType shiftUsage = Shift_RequireOff) + : Key(key), ShiftUsage(shiftUsage) { } + + OVR::KeyCode Key; + ShiftUsageType ShiftUsage; +}; + + +//------------------------------------------------------------------------------------- +struct OptionShortcut +{ + Array<ShortcutKey> Keys; + Array<UInt32> GamepadButtons; + FunctionNotifyBase* pNotify; + + OptionShortcut() : pNotify(NULL) {} + OptionShortcut(FunctionNotifyBase* pNotify) : pNotify(pNotify) {} + ~OptionShortcut() { if (pNotify) delete pNotify; } + + void AddShortcut(ShortcutKey key) { Keys.PushBack(key); } + void AddShortcut(UInt32 gamepadButton) { GamepadButtons.PushBack(gamepadButton); } + + bool MatchKey(OVR::KeyCode key, bool shift) const; + bool MatchGamepadButton(UInt32 gamepadButtonMask) const; +}; + + +//------------------------------------------------------------------------------------- + +// Base class for a menu item. Internally, this can also be OptionSelectionMenu itself +// to support nested menus. Users shouldn't need to use this class. +class OptionMenuItem : public NewOverrideBase +{ +public: + virtual ~OptionMenuItem() { } + + virtual void Select() { } + + virtual void NextValue(bool* pFastStep = NULL) { OVR_UNUSED1(pFastStep); } + virtual void PrevValue(bool* pFastStep = NULL) { OVR_UNUSED1(pFastStep); } + + virtual String GetLabel() { return Label; } + virtual String GetValue() { return ""; } + + // Returns empty string if shortcut not handled + virtual String ProcessShortcutKey(OVR::KeyCode key, bool shift) = 0; + virtual String ProcessShortcutButton(UInt32 buttonMask) = 0; + + virtual bool IsMenu() const { return false; } + +protected: + String Label; + String PopNamespaceFrom(OptionMenuItem* menuItem); +}; + + +//------------------------------------------------------------------------------------- + +// OptionVar implements a basic menu item, which binds to an external variable, +// displaying and editing its state. +class OptionVar : public OptionMenuItem +{ +public: + + enum VarType + { + Type_Enum, + Type_Int, + Type_Float, + Type_Bool + }; + + typedef String (*FormatFunction)(OptionVar*); + typedef void (*UpdateFunction)(OptionVar*); + + static String FormatEnum(OptionVar* var); + static String FormatInt(OptionVar* var); + static String FormatFloat(OptionVar* var); + static String FormatTan(OptionVar* var); + static String FormatBool(OptionVar* var); + + OptionVar(const char* name, void* pVar, VarType type, + FormatFunction formatFunction, + UpdateFunction updateFunction = NULL); + + // Integer with range and step size. + OptionVar(const char* name, SInt32* pVar, + SInt32 min, SInt32 max, SInt32 stepSize=1, + const char* formatString = "%d", + FormatFunction formatFunction = 0, // Default int formatting. + UpdateFunction updateFunction = NULL); + + // Float with range and step size. + OptionVar(const char* name, float* pvar, + float minf, float maxf, float stepSize = 1.0f, + const char* formatString = "%.3f", float formatScale = 1.0f, + FormatFunction formatFunction = 0, // Default float formatting. + UpdateFunction updateFunction = 0 ); + + virtual ~OptionVar(); + + SInt32* AsInt() { return reinterpret_cast<SInt32*>(pVar); } + bool* AsBool() { return reinterpret_cast<bool*>(pVar); } + float* AsFloat() { return reinterpret_cast<float*>(pVar); } + VarType GetType() { return Type; } + + // Step through values (wrap for enums). + virtual void NextValue(bool* pFastStep); + virtual void PrevValue(bool* pFastStep); + + // Executes shortcut message and returns notification string. + // Returns empty string for no action. + String HandleShortcutUpdate(); + virtual String ProcessShortcutKey(OVR::KeyCode key, bool shift); + virtual String ProcessShortcutButton(UInt32 buttonMask); + + OptionVar& AddEnumValue(const char* displayName, SInt32 value); + + template<class C> + OptionVar& SetNotify(C* p, void (C::*fn)(OptionVar*)) + { + OVR_ASSERT(pNotify == 0); // Can't set notifier twice. + pNotify = new FunctionNotifyContext<C, OptionVar>(p, fn, this); + return *this; + } + + + //String Format(); + virtual String GetValue(); + + OptionVar& AddShortcutUpKey(const ShortcutKey& shortcut) + { ShortcutUp.AddShortcut(shortcut); return *this; } + OptionVar& AddShortcutUpKey(OVR::KeyCode key, + ShortcutKey::ShiftUsageType shiftUsage = ShortcutKey::Shift_Modify) + { ShortcutUp.AddShortcut(ShortcutKey(key, shiftUsage)); return *this; } + OptionVar& AddShortcutUpButton(UInt32 gamepadButton) + { ShortcutUp.AddShortcut(gamepadButton); return *this; } + + OptionVar& AddShortcutDownKey(const ShortcutKey& shortcut) + { ShortcutDown.AddShortcut(shortcut); return *this; } + OptionVar& AddShortcutDownKey(OVR::KeyCode key, + ShortcutKey::ShiftUsageType shiftUsage = ShortcutKey::Shift_Modify) + { ShortcutDown.AddShortcut(ShortcutKey(key, shiftUsage)); return *this; } + OptionVar& AddShortcutDownButton(UInt32 gamepadButton) + { ShortcutDown.AddShortcut(gamepadButton); return *this; } + + OptionVar& AddShortcutKey(const ShortcutKey& shortcut) + { return AddShortcutUpKey(shortcut); } + OptionVar& AddShortcutKey(OVR::KeyCode key, + ShortcutKey::ShiftUsageType shiftUsage = ShortcutKey::Shift_RequireOff) + { return AddShortcutUpKey(key, shiftUsage); } + OptionVar& AddShortcutButton(UInt32 gamepadButton) + { return AddShortcutUpButton(gamepadButton); } + +private: + + void SignalUpdate() + { + if (fUpdate) fUpdate(this); + if (pNotify) pNotify->CallNotify(this); + } + + struct EnumEntry + { + // Human readable name for enum. + String Name; + SInt32 Value; + }; + + // Array of possible enum values. + Array<EnumEntry> EnumValues; + // Gets the index of the current enum value. + UInt32 GetEnumIndex(); + + FormatFunction fFormat; + UpdateFunction fUpdate; + FunctionNotifyBase* pNotify; + + VarType Type; + void* pVar; + const char* FormatString; + + OptionShortcut ShortcutUp; + OptionShortcut ShortcutDown; + + float MinFloat; + float MaxFloat; + float StepFloat; + float FormatScale; // Multiply float by this before rendering + + SInt32 MinInt; + SInt32 MaxInt; + SInt32 StepInt; +}; + + +//------------------------------------------------------------------------------------- +// ***** OptionSelectionMenu + +// Implements an overlay option menu, brought up by the 'Tab' key. +// Items are added to the menu with AddBool, AddEnum, AddFloat on startup, +// and are editable by using arrow keys (underlying variable is modified). +// +// Call Render() to render the menu every frame. +// +// Menu also support displaying popup messages with a timeout, displayed +// when menu body isn't up. + +class OptionSelectionMenu : public OptionMenuItem +{ +public: + OptionSelectionMenu(OptionSelectionMenu* parentMenu = NULL); + ~OptionSelectionMenu(); + + + bool OnKey(OVR::KeyCode key, int chr, bool down, int modifiers); + bool OnGamepad(UInt32 buttonMask); + + void Render(RenderDevice* prender, String title = ""); + + void AddItem(OptionMenuItem* menuItem); + + // Adds a boolean toggle. Returns added item to allow customization. + OptionVar& AddBool(const char* name, bool* pvar, + OptionVar::UpdateFunction updateFunction = 0, + OptionVar::FormatFunction formatFunction = OptionVar::FormatBool) + { + OptionVar* p = new OptionVar(name, pvar, OptionVar::Type_Bool, + formatFunction, updateFunction); + AddItem(p); + return *p; + } + + // Adds a boolean toggle. Returns added item to allow customization. + OptionVar& AddEnum(const char* name, void* pvar, + OptionVar::UpdateFunction updateFunction = 0) + { + OptionVar* p = new OptionVar(name, pvar, OptionVar::Type_Enum, + OptionVar::FormatEnum, updateFunction); + AddItem(p); + return *p; + } + + + // Adds a Float variable. Returns added item to allow customization. + OptionVar& AddFloat( const char* name, float* pvar, + float minf, float maxf, float stepSize = 1.0f, + const char* formatString = "%.3f", float formatScale = 1.0f, + OptionVar::FormatFunction formatFunction = 0, // Default float formatting. + OptionVar::UpdateFunction updateFunction = 0 ) + { + OptionVar* p = new OptionVar(name, pvar, minf, maxf, stepSize, + formatString, formatScale, + formatFunction, updateFunction); + AddItem(p); + return *p; + } + + virtual void Select(); + virtual String GetLabel() { return Label + " >"; } + + virtual String ProcessShortcutKey(OVR::KeyCode key, bool shift); + virtual String ProcessShortcutButton(UInt32 buttonMask); + + // Sets a message to display with a time-out. Default time-out is 4 seconds. + // This uses the same overlay approach as used for shortcut notifications. + void SetPopupMessage(const char* format, ...); + // Overrides current timeout, in seconds (not the future default value); + // intended to be called right after SetPopupMessage. + void SetPopupTimeout(double timeoutSeconds, bool border = false); + + virtual bool IsMenu() const { return true; } + +protected: + + void renderShortcutChangeMessage(RenderDevice* prender); + +public: + OptionSelectionMenu* GetSubmenu(); + OptionSelectionMenu* GetOrCreateSubmenu(String submenuName); + + enum DisplayStateType + { + Display_None, + Display_Menu, + Display_SingleItem + }; + + DisplayStateType DisplayState; + OptionSelectionMenu* ParentMenu; + + ArrayPOD<OptionMenuItem*> Items; + int SelectedIndex; + bool SelectionActive; + + String PopupMessage; + double PopupMessageTimeout; + bool PopupMessageBorder; + + // Possible menu navigation actions. + enum NavigationActions + { + Nav_Up, + Nav_Down, + Nav_Left, + Nav_Right, + Nav_Select, + Nav_Back, + Nav_LAST + }; + + // Handlers for navigation actions. + void HandleUp(bool* pFast); + void HandleDown(bool* pFast); + void HandleLeft(); + void HandleRight(); + void HandleSelect(); + void HandleBack(); + + void HandleMenuToggle(); + void HandleSingleItemToggle(); + + OptionShortcut NavShortcuts[Nav_LAST]; + OptionShortcut ToggleShortcut; + OptionShortcut ToggleSingleItemShortcut; +}; + + +//------------------------------------------------------------------------------------- +// Text Rendering Utility +enum DrawTextCenterType +{ + DrawText_NoCenter= 0, + DrawText_VCenter = 0x01, + DrawText_HCenter = 0x02, + DrawText_Center = DrawText_VCenter | DrawText_HCenter, + DrawText_Border = 0x10, +}; + +void DrawTextBox(RenderDevice* prender, float x, float y, + float textSize, const char* text, + unsigned centerType = DrawText_NoCenter); + +void CleanupDrawTextFont(); + + +#endif // INC_OptionMenu_h diff --git a/Samples/CommonSrc/Util/RenderProfiler.cpp b/Samples/CommonSrc/Util/RenderProfiler.cpp new file mode 100644 index 0000000..00bbdd9 --- /dev/null +++ b/Samples/CommonSrc/Util/RenderProfiler.cpp @@ -0,0 +1,99 @@ +/************************************************************************************ + +Filename : RenderProfiler.cpp +Content : Profiling for render. +Created : March 10, 2014 +Authors : Caleb Leak + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*************************************************************************************/ + +#include "RenderProfiler.h" + +using namespace OVR; + +RenderProfiler::RenderProfiler() +{ + memset(SampleHistory, 0, sizeof(SampleHistory)); + memset(SampleAverage, 0, sizeof(SampleAverage)); + SampleCurrentFrame = 0; +} + +void RenderProfiler::RecordSample(SampleType sampleType) +{ + if (sampleType == Sample_FrameStart) + { + // Recompute averages and subtract off frame start time. + for (int sample = 1; sample < Sample_LAST; sample++) + { + SampleHistory[SampleCurrentFrame][sample] -= SampleHistory[SampleCurrentFrame][0]; + + // Recompute the average for the current sample type. + SampleAverage[sample] = 0.0; + for (int frame = 0; frame < NumFramesOfTimerHistory; frame++) + { + SampleAverage[sample] += SampleHistory[frame][sample]; + } + SampleAverage[sample] /= NumFramesOfTimerHistory; + } + + SampleCurrentFrame = ((SampleCurrentFrame + 1) % NumFramesOfTimerHistory); + } + + SampleHistory[SampleCurrentFrame][sampleType] = ovr_GetTimeInSeconds(); +} + +const double* RenderProfiler::GetLastSampleSet() const +{ + return SampleHistory[(SampleCurrentFrame - 1 + NumFramesOfTimerHistory) % NumFramesOfTimerHistory]; +} + +void RenderProfiler::DrawOverlay(RenderDevice* prender) +{ + char buf[256 * Sample_LAST]; + OVR_strcpy ( buf, sizeof(buf), "Timing stats" ); // No trailing \n is deliberate. + + /*int timerLastFrame = TimerCurrentFrame - 1; + if ( timerLastFrame < 0 ) + { + timerLastFrame = NumFramesOfTimerHistory - 1; + }*/ + // Timer 0 is always the time at the start of the frame. + + const double* averages = GetAverages(); + const double* lastSampleSet = GetLastSampleSet(); + + for ( int timerNum = 1; timerNum < Sample_LAST; timerNum++ ) + { + char const *pName = ""; + switch ( timerNum ) + { + case Sample_AfterGameProcessing: pName = "AfterGameProcessing"; break; + case Sample_AfterEyeRender : pName = "AfterEyeRender "; break; +// case Sample_BeforeDistortion : pName = "BeforeDistortion "; break; +// case Sample_AfterDistortion : pName = "AfterDistortion "; break; + case Sample_AfterPresent : pName = "AfterPresent "; break; +// case Sample_AfterFlush : pName = "AfterFlush "; break; + default: OVR_ASSERT ( false ); + } + char bufTemp[256]; + OVR_sprintf ( bufTemp, sizeof(bufTemp), "\nRaw: %.2lfms\t400Ave: %.2lfms\t800%s", + lastSampleSet[timerNum] * 1000.0, averages[timerNum] * 1000.0, pName ); + OVR_strcat ( buf, sizeof(buf), bufTemp ); + } + + DrawTextBox(prender, 0.0f, 0.0f, 22.0f, buf, DrawText_Center); +}
\ No newline at end of file diff --git a/Samples/CommonSrc/Util/RenderProfiler.h b/Samples/CommonSrc/Util/RenderProfiler.h new file mode 100644 index 0000000..96ec50a --- /dev/null +++ b/Samples/CommonSrc/Util/RenderProfiler.h @@ -0,0 +1,71 @@ +/************************************************************************************ + +Filename : RenderProfiler.h +Content : Profiling for render. +Created : March 10, 2014 +Authors : Caleb Leak + +Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*************************************************************************************/ + +#ifndef INC_RenderProfiler_h +#define INC_RenderProfiler_h + +#include "OVR.h" + +// TODO: Refactor option menu so dependencies are in a separate file. +#include "OptionMenu.h" + +//------------------------------------------------------------------------------------- +// ***** RenderProfiler + +// Tracks reported timing sample in a frame and dislays them an overlay from DrawOverlay(). +class RenderProfiler +{ +public: + enum { NumFramesOfTimerHistory = 10 }; + + enum SampleType + { + Sample_FrameStart , + Sample_AfterGameProcessing , + Sample_AfterEyeRender , + // Sample_BeforeDistortion , + // Sample_AfterDistortion , + Sample_AfterPresent , + // Sample_AfterFlush , + + Sample_LAST + }; + + RenderProfiler(); + + // Records the current time for the given sample type. + void RecordSample(SampleType sampleType); + + const double* GetAverages() const { return SampleAverage; } + const double* GetLastSampleSet() const; + + void DrawOverlay(RenderDevice* prender); + +private: + + double SampleHistory[NumFramesOfTimerHistory][Sample_LAST]; + double SampleAverage[Sample_LAST]; + int SampleCurrentFrame; +}; + +#endif // INC_RenderProfiler_h |