added option search menu

This commit is contained in:
Alexander 2019-03-16 14:46:48 +07:00 committed by Christoph Oelckers
parent 540e180fb1
commit 73d81d3983
6 changed files with 368 additions and 1 deletions

View File

@ -51,6 +51,9 @@ CVAR(Int, developer, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
// [RH] Feature control cvars
CVAR(Bool, var_friction, true, CVAR_SERVERINFO);
// Option Search
CVAR(Bool, os_isanyof, true, CVAR_ARCHIVE);
@ -138,4 +141,3 @@ CUSTOM_CVAR(String, language, "auto", CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOB
if (Level->info != nullptr) Level->LevelName = Level->info->LookupLevelName();
}
}

View File

@ -378,6 +378,8 @@ OptionMenu "OptionsMenu" protected
Submenu "$OPTMNU_DISPLAY", "VideoOptions"
Submenu "$OPTMNU_VIDEO", "VideoModeMenu"
StaticText " "
Submenu "$OS_TITLE", "os_Menu"
StaticText " "
SafeCommand "$OPTMNU_DEFAULTS", "reset2defaults"
SafeCommand "$OPTMNU_RESETTOSAVED", "reset2saved"
Command "$OPTMNU_CONSOLE", "menuconsole"
@ -2606,3 +2608,21 @@ OptionString "LanguageOptions"
"ptb", "Português do Brasil (PTB)"
"rus", "Русский (RU)"
}
/*=======================================
*
* Option Search menu
*
*=======================================*/
OptionMenu "os_Menu"
{
Class "os_Menu"
Title "$OS_TITLE"
}
OptionValue "os_isanyof_values"
{
0, "$OS_ALL"
1, "$OS_ANY"
}

View File

@ -253,6 +253,10 @@ version "3.8"
#include "zscript/ui/menu/reverbedit.zs"
#include "zscript/ui/menu/textentermenu.zs"
#include "zscript/ui/menu/search/menu.zs"
#include "zscript/ui/menu/search/searchfield.zs"
#include "zscript/ui/menu/search/query.zs"
#include "zscript/ui/statscreen/statscreen.zs"
#include "zscript/ui/statscreen/statscreen_coop.zs"
#include "zscript/ui/statscreen/statscreen_dm.zs"

View File

@ -0,0 +1,212 @@
//=============================================================================
//
// Option Search Menu class.
// This menu contains search field, and is dynamically filled with search
// results.
//
//=============================================================================
class os_Menu : OptionMenu
{
override void Init(Menu parent, OptionMenuDescriptor desc)
{
Super.Init(parent, desc);
mDesc.mItems.clear();
addSearchField();
mDesc.mScrollPos = 0;
mDesc.mSelectedItem = 0;
mDesc.CalcIndent();
}
void search(os_Query query)
{
mDesc.mItems.clear();
addSearchField(query.getText());
string path = StringTable.Localize("$OPTMNU_TITLE");
bool isAnyTermMatches = mIsAnyOfItem.mCVar.GetBool();
bool found = listOptions(mDesc, "MainMenu", query, path, isAnyTermMatches);
if (!found) { addNoResultsItem(mDesc); }
mDesc.CalcIndent();
}
private void addSearchField(string query = "")
{
string searchLabel = StringTable.Localize("$OS_LABEL");
let searchField = new("os_SearchField").Init(searchLabel, self, query);
mIsAnyOfItem = new("OptionMenuItemOption").Init("", "os_isanyof", "os_isanyof_values");
mDesc.mItems.push(searchField);
mDesc.mItems.push(mIsAnyOfItem);
addEmptyLine(mDesc);
}
private static bool listOptions(OptionMenuDescriptor targetDesc,
string menuName,
os_Query query,
string path,
bool isAnyTermMatches)
{
let desc = MenuDescriptor.GetDescriptor(menuName);
let listMenuDesc = ListMenuDescriptor(desc);
if (listMenuDesc)
{
return listOptionsListMenu(listMenuDesc, targetDesc, query, path, isAnyTermMatches);
}
let optionMenuDesc = OptionMenuDescriptor(desc);
if (optionMenuDesc)
{
return listOptionsOptionMenu(optionMenuDesc, targetDesc, query, path, isAnyTermMatches);
}
return false;
}
private static bool listOptionsListMenu(ListMenuDescriptor sourceDesc,
OptionMenuDescriptor targetDesc,
os_Query query,
string path,
bool isAnyTermMatches)
{
int nItems = sourceDesc.mItems.size();
bool found = false;
for (int i = 0; i < nItems; ++i)
{
let item = sourceDesc.mItems[i];
string actionN = item.GetAction();
string newPath = (actionN == "Optionsmenu")
? StringTable.Localize("$OPTMNU_TITLE")
: StringTable.Localize("$OS_MAIN");
found |= listOptions(targetDesc, actionN, query, newPath, isAnyTermMatches);
}
return found;
}
private static bool listOptionsOptionMenu(OptionMenuDescriptor sourceDesc,
OptionMenuDescriptor targetDesc,
os_Query query,
string path,
bool isAnyTermMatches)
{
if (sourceDesc == targetDesc) { return false; }
int nItems = sourceDesc.mItems.size();
bool first = true;
bool found = false;
for (int i = 0; i < nItems; ++i)
{
let item = sourceDesc.mItems[i];
if (item is "OptionMenuItemStaticText") { continue; }
string label = StringTable.Localize(item.mLabel);
if (!query.matches(label, isAnyTermMatches)) { continue; }
found = true;
if (first)
{
addEmptyLine(targetDesc);
addPathItem(targetDesc, path);
first = false;
}
let itemOptionBase = OptionMenuItemOptionBase(item);
if (itemOptionBase)
{
itemOptionBase.mCenter = false;
}
targetDesc.mItems.push(item);
}
for (int i = 0; i < nItems; ++i)
{
let item = sourceDesc.mItems[i];
string label = StringTable.Localize(item.mLabel);
string optionSearchTitle = StringTable.Localize("$OS_TITLE");
if (label == optionSearchTitle) { continue; }
if (item is "OptionMenuItemSubMenu")
{
string newPath = makePath(path, label);
found |= listOptions(targetDesc, item.GetAction(), query, newPath, isAnyTermMatches);
}
}
return found;
}
private static string makePath(string path, string label)
{
int pathWidth = SmallFont.StringWidth(path .. "/" .. label);
int screenWidth = Screen.GetWidth();
bool isTooWide = (pathWidth > screenWidth / 3);
string newPath = isTooWide
? path .. "/" .. "\n" .. label
: path .. "/" .. label;
return newPath;
}
private static void addPathItem(OptionMenuDescriptor desc, string path)
{
Array<String> lines;
path.split(lines, "\n");
int nLines = lines.size();
for (int i = 0; i < nLines; ++i)
{
OptionMenuItemStaticText text = new("OptionMenuItemStaticText").Init(lines[i], 1);
desc.mItems.push(text);
}
}
private static void addEmptyLine(OptionMenuDescriptor desc)
{
int nItems = desc.mItems.size();
if (nItems > 0)
{
let staticText = OptionMenuItemStaticText(desc.mItems[nItems - 1]);
if (staticText != null && staticText.mLabel == "") { return; }
}
let item = new("OptionMenuItemStaticText").Init("");
desc.mItems.push(item);
}
private static void addNoResultsItem(OptionMenuDescriptor desc)
{
string noResults = StringTable.Localize("$OS_NO_RESULTS");
let text = new("OptionMenuItemStaticText").Init(noResults, 0);
addEmptyLine(desc);
desc.mItems.push(text);
}
private OptionMenuItemOption mIsAnyOfItem;
}

View File

@ -0,0 +1,78 @@
//=============================================================================
//
// Option Search Query class represents a search query.
// A search query consists constists of one or more terms (words).
//
// Query matching deponds on "os_is_any_of" variable.
// If this variable is "true", the text matches the query if any of the terms
// matches the query.
// If this variable is "false", the text matches the query only if all the
// terms match the query.
//
//=============================================================================
class os_Query
{
static os_Query fromString(string str)
{
let query = new("os_Query");
str.Split(query.mQueryParts, " ", TOK_SKIPEMPTY);
query.mText = str;
return query;
}
bool matches(string text, bool isSearchForAny)
{
return isSearchForAny
? matchesAny(text)
: matchesAll(text);
}
string getText() { return mText; }
// private: //////////////////////////////////////////////////////////////////
private bool matchesAny(string text)
{
int nParts = mQueryParts.size();
for (int i = 0; i < nParts; ++i)
{
string queryPart = mQueryParts[i];
if (contains(text, queryPart)) { return true; }
}
return false;
}
private bool matchesAll(string text)
{
int nParts = mQueryParts.size();
for (int i = 0; i < nParts; ++i)
{
string queryPart = mQueryParts[i];
if (!contains(text, queryPart)) { return false; }
}
return true;
}
private static bool contains(string str, string substr)
{
str .toLower();
substr.toLower();
bool contains = (str.IndexOf(substr) != -1);
return contains;
}
private string mText;
private Array<String> mQueryParts;
}

View File

@ -0,0 +1,51 @@
//=============================================================================
//
// Option Search Field class.
//
// When the search query is entered, makes Search Menu perform a search.
//
//=============================================================================
class os_SearchField : OptionMenuItemTextField
{
os_SearchField Init(String label, os_Menu menu, string query)
{
Super.Init(label, "");
mMenu = menu;
mText = query;
return self;
}
override bool MenuEvent(int mkey, bool fromcontroller)
{
if (mkey == Menu.MKEY_Enter)
{
Menu.MenuSound("menu/choose");
mEnter = TextEnterMenu.OpenTextEnter(Menu.GetCurrentMenu(), SmallFont, mText, -1, fromcontroller);
mEnter.ActivateMenu();
return true;
}
if (mkey == Menu.MKEY_Input)
{
string text = mEnter.GetText();
let query = os_Query.fromString(text);
mMenu.search(query);
}
return Super.MenuEvent(mkey, fromcontroller);
}
override String Represent()
{
return mEnter
? mEnter.GetText() .. SmallFont.GetCursor()
: mText;
}
private os_Menu mMenu;
private string mText;
}