From 1dc8488ca613db47e5abed40b21a1893538f571c Mon Sep 17 00:00:00 2001
From: Crypto City <cryptocity@example.com>
Date: Fri, 17 Jan 2020 01:44:59 +0000
Subject: [PATCH] game: wip - switch to turbo badger for ui

---
 GameData/TB/cc/research-row.tb.txt |  13 +
 GameData/TB/cc/research.tb.txt     |  34 ++
 src/game/ui-research.cc            | 515 ++++++++++-------------------
 src/game/ui-research.h             |  69 ++--
 4 files changed, 265 insertions(+), 366 deletions(-)
 create mode 100644 GameData/TB/cc/research-row.tb.txt
 create mode 100644 GameData/TB/cc/research.tb.txt

diff --git a/GameData/TB/cc/research-row.tb.txt b/GameData/TB/cc/research-row.tb.txt
new file mode 100644
index 000000000..46a09e65f
--- /dev/null
+++ b/GameData/TB/cc/research-row.tb.txt
@@ -0,0 +1,13 @@
+WindowInfo
+
+TBLayout: axis: x, size: "available", distribution: "gravity"
+	TBLayout: axis: y, distribution: "preferred", position: "left top"
+		TBTextField: id: "name"
+		TBTextField: id: "status"
+		TBTextField: id: "desc"
+	TBLayout: axis: y, distribution: "preferred", position: "left top", gravity: "all"
+		TBTextField: text: "", gravity: "left right"
+	TBLayout: axis: y, id: "researching", size: "preferred"
+		TBEditField: id: "amount", text: "0", value: 1
+		TBTextField: id: "chance", text: "0% chance", value: 1
+		TBButton: id: "research", text: "Research", value: 1
diff --git a/GameData/TB/cc/research.tb.txt b/GameData/TB/cc/research.tb.txt
new file mode 100644
index 000000000..ae0dc10b0
--- /dev/null
+++ b/GameData/TB/cc/research.tb.txt
@@ -0,0 +1,34 @@
+WindowInfo
+	title Research
+	modal: 1
+
+TBLayout: axis: y, distribution-position: "left top", distribution: "available"
+
+	TBLayout: axis x, distribution-position: "left top", distribution: "available"
+
+		TBLayout: axis: y
+			TBLayout: axis: x, distribution-position: "left"
+				TBTextField: text: "Balance:"
+				TBTextField: id: "balance", text: ""
+			TBLayout: axis: x, distribution-position: "left"
+				TBTextField: text: "Research bonus:"
+				TBTextField: id: "research-bonus", text: ""
+			TBLayout: axis: x, distribution-position: "left"
+				TBTextField: text: "Discovered by you:"
+				TBTextField: id: "num-discoveries", text: ""
+
+		TBLayout: axis: y
+			TBLayout: axis: x, distribution-position: "left"
+				TBCheckBox: id: "show-discovered"
+				TBTextField: text: "Discovered"
+			TBLayout: axis: x, distribution-position: "left"
+				TBCheckBox: id: "show-researching"
+				TBTextField: text: "Researching"
+			TBLayout: axis: x, distribution-position: "left"
+				TBCheckBox: id: "show-locked"
+				TBTextField: text: "Locked"
+
+	TBEditField: id: "search", placeholder: "search", type: "search", gravity: "left right"
+
+	TBLayout: axis: y, distribution: "available"
+		TBSelectList: id: "discoveries", gravity: "all"
diff --git a/src/game/ui-research.cc b/src/game/ui-research.cc
index d5b12ba6a..bcaaaf60d 100644
--- a/src/game/ui-research.cc
+++ b/src/game/ui-research.cc
@@ -9,13 +9,10 @@
 #include "Urho3D/Graphics/Graphics.h"
 #include "Urho3D/UI/UI.h"
 #include "Urho3D/UI/UIEvents.h"
-#include "Urho3D/UI/Window.h"
-#include "Urho3D/UI/DropDownList.h"
-#include "Urho3D/UI/LineEdit.h"
-#include "Urho3D/UI/Text.h"
-#include "Urho3D/UI/Button.h"
-#include "Urho3D/UI/ListView.h"
-#include "Urho3D/UI/CheckBox.h"
+#include <tb/tb_widgets_common.h>
+#include <tb/tb_widgets_reader.h>
+#include <tb/tb_editfield.h>
+#include <tb/tb_select.h>
 #include "ui-tb-message-box.h"
 #include "game/game-state.h"
 #include "game/game-wallet.h"
@@ -23,19 +20,7 @@
 #include "ui-research.h"
 
 using namespace Urho3D;
-
-#define HEADER_WIDTH 160
-
-static SharedPtr<Text> AddText(SharedPtr<UIElement> e, const char *s, float scale = 1.0f)
-{
-  SharedPtr<Text> text(new Text(e->GetContext()));
-  text->SetStyleAuto();
-  text->SetText(s);
-  text->SetName("text");
-  e->AddChild(text);
-  text->SetFontSize(text->GetFontSize() * scale);
-  return text;
-}
+using namespace tb;
 
 static String get_place_string(size_t n)
 {
@@ -47,215 +32,19 @@ static String get_place_string(size_t n)
   return String(n) + postfix;
 }
 
-UIResearchDialog::UIResearchDialog(Context *ctx, const GameState *game):
-  Object(ctx),
-  game(game),
-  research_bonus(0)
+UIResearchDialog::DiscoveryWidget::DiscoveryWidget(UIResearchDialog::DiscoveryItem *item, DiscoverySource *source, tb::TBSelectItemViewer *source_viewer, int index):
+  m_source(source),
+  m_source_viewer(source_viewer),
+  m_index(index)
 {
-  auto* graphics = GetSubsystem<Graphics>();
-  auto root = ctx->GetSubsystem<UI>()->GetRoot();
-  auto* cache = ctx->GetSubsystem<ResourceCache>();
-  auto* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
-  root->SetDefaultStyle(style);
+  SetSkinBg(TBIDC("TBSelectItem"));
+  SetLayoutDistribution(LAYOUT_DISTRIBUTION_GRAVITY);
+  SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
+  SetPaintOverflowFadeout(false);
 
-  window = root->CreateChild<Window>();
-  window->SetName("UIResearchDialog");
-  window->SetMinWidth(graphics->GetWidth() * 0.85f);
-  window->SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6));
-  window->SetAlignment(HA_CENTER, VA_CENTER);
-  window->SetStyleAuto(style);
+  g_widgets_reader->LoadFile(GetContentRoot(), "cc/research-row.tb.txt");
 
-  // Create Window 'titlebar' container
-  auto* titleBar = new UIElement(context_);
-  titleBar->SetMinSize(0, 24);
-  titleBar->SetVerticalAlignment(VA_TOP);
-  titleBar->SetLayoutMode(LM_HORIZONTAL);
-
-  // Create the Window title Text
-  auto *windowTitle_ = new Text(context_);
-  windowTitle_->SetName("WindowTitle");
-  windowTitle_->SetText("Research");
-  windowTitle_->SetStyleAuto(style);
-
-  // Create the Window's close button
-  auto* buttonClose = new Button(context_);
-  buttonClose->SetName("CloseButton");
-  buttonClose->SetStyle("CloseButton");
-
-  // Add the controls to the title bar
-  titleBar->AddChild(windowTitle_);
-  titleBar->AddChild(buttonClose);
-
-  // Add the title bar to the Window
-  window->AddChild(titleBar);
-
-  // header
-  SharedPtr<UIElement> headerLayout(new UIElement(context_));
-  headerLayout->SetStyleAuto(style);
-  headerLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 12, 0, 2));
-  window->AddChild(headerLayout);
-
-  // Player data
-  SharedPtr<UIElement> playerDataLayout(new UIElement(context_));
-  playerDataLayout->SetStyleAuto(style);
-  playerDataLayout->SetLayout(LM_VERTICAL, 6, IntRect(0, 12, 0, 2));
-  headerLayout->AddChild(playerDataLayout);
-
-  // Balance
-  SharedPtr<UIElement> balanceLayout(new UIElement(context_));
-  balanceLayout->SetStyleAuto(style);
-  balanceLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  playerDataLayout->AddChild(balanceLayout);
-  AddText(balanceLayout, "Balance:")->SetMaxWidth(HEADER_WIDTH);
-  balanceWidget = AddText(balanceLayout, "0");
-
-  // Research bonus
-  SharedPtr<UIElement> researchBonusLayout(new UIElement(context_));
-  researchBonusLayout->SetStyleAuto(style);
-  researchBonusLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  playerDataLayout->AddChild(researchBonusLayout);
-  AddText(researchBonusLayout, "Research bonus:")->SetMaxWidth(HEADER_WIDTH);
-  researchBonusWidget = AddText(researchBonusLayout, "0");
-
-  // Number of discoveries
-  SharedPtr<UIElement> numDiscoveriesLayout(new UIElement(context_));
-  numDiscoveriesLayout->SetStyleAuto(style);
-  numDiscoveriesLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  playerDataLayout->AddChild(numDiscoveriesLayout);
-  AddText(numDiscoveriesLayout, "Discovered by you:")->SetMaxWidth(HEADER_WIDTH);
-  numDiscoveriesWidget = AddText(numDiscoveriesLayout, "0");
-
-  // Filters
-  SharedPtr<UIElement> filtersLayout(new UIElement(context_));
-  filtersLayout->SetStyleAuto(style);
-  filtersLayout->SetLayout(LM_VERTICAL, 6, IntRect(0, 12, 0, 2));
-  filtersLayout->SetMaxWidth(window->GetWidth() / 4);
-  headerLayout->AddChild(filtersLayout);
-
-  // Discovered
-  SharedPtr<UIElement> showDiscoveredLayout(new UIElement(context_));
-  showDiscoveredLayout->SetStyleAuto(style);
-  showDiscoveredLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  filtersLayout->AddChild(showDiscoveredLayout);
-  showDiscoveredWidget = new CheckBox(context_);
-  showDiscoveredWidget->SetStyleAuto(style);
-  showDiscoveredWidget->SetChecked(false);
-  showDiscoveredLayout->AddChild(showDiscoveredWidget);
-  AddText(showDiscoveredLayout, "Show discovered");
-
-  // Researching
-  SharedPtr<UIElement> showResearchingLayout(new UIElement(context_));
-  showResearchingLayout->SetStyleAuto(style);
-  showResearchingLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  filtersLayout->AddChild(showResearchingLayout);
-  showResearchingWidget = new CheckBox(context_);
-  showResearchingWidget->SetStyleAuto(style);
-  showResearchingWidget->SetChecked(true);
-  showResearchingLayout->AddChild(showResearchingWidget);
-  AddText(showResearchingLayout, "Show researching");
-
-  // Locked
-  SharedPtr<UIElement> showLockedLayout(new UIElement(context_));
-  showLockedLayout->SetStyleAuto(style);
-  showLockedLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  filtersLayout->AddChild(showLockedLayout);
-  showLockedWidget = new CheckBox(context_);
-  showLockedWidget->SetStyleAuto(style);
-  showLockedWidget->SetChecked(false);
-  showLockedLayout->AddChild(showLockedWidget);
-  AddText(showLockedLayout, "Show locked");
-
-  // Search
-  SharedPtr<UIElement> searchLayout(new UIElement(context_));
-  searchLayout->SetStyleAuto(style);
-  searchLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 2, 0, 2));
-  window->AddChild(searchLayout);
-  AddText(searchLayout, "Search:")->SetMaxWidth(HEADER_WIDTH);
-  searchWidget = new LineEdit(context_);
-  searchWidget->SetName("searchBox");
-  searchWidget->SetMinHeight(24);
-  searchWidget->SetStyleAuto(style);
-  searchLayout->AddChild(searchWidget);
-
-  // Discoveries
-  discoveriesListWidget = new ListView(context_);
-  discoveriesListWidget->SetStyleAuto(style);
-  discoveriesListWidget->SetMinHeight(480);
-  window->AddChild(discoveriesListWidget);
-
-  UpdateDiscoveriesList();
-
-  // OK/cancel buttons
-  auto* horiz = new UIElement(context_);
-  horiz->SetLayout(LM_HORIZONTAL, 6, IntRect(0, 6, 0, 0));
-  window->AddChild(horiz);
-
-  // OK
-  auto* buttonOK = new Button(context_);
-  buttonOK->SetName("OK");
-  buttonOK->SetMinHeight(24);
-  buttonOK->SetStyleAuto(style);
-  horiz->AddChild(buttonOK);
-  Text *textOK = new Text(context_);
-  textOK->SetText("OK");
-  textOK->SetAlignment(HA_CENTER, VA_CENTER);
-  textOK->SetStyleAuto(style);
-  buttonOK->AddChild(textOK);
-
-  // Cancel
-  auto* buttonCancel = new Button(context_);
-  buttonCancel->SetName("Cancel");
-  buttonCancel->SetMinHeight(24);
-  buttonCancel->SetStyleAuto(style);
-  horiz->AddChild(buttonCancel);
-  Text *textCancel = new Text(context_);
-  textCancel->SetText("Cancel");
-  textCancel->SetAlignment(HA_CENTER, VA_CENTER);
-  textCancel->SetStyleAuto(style);
-  buttonCancel->AddChild(textCancel);
-
-  //const IntVector2 size = window->GetSize();
-  window->SetPosition(0, 0);
-  window->SetModal(true);
-
-  buttonClose->SetStyle("CloseButton");
-
-  SubscribeToEvent(window, E_MODALCHANGED, URHO3D_HANDLER(UIResearchDialog, HandleCancel));
-  SubscribeToEvent(buttonOK, E_RELEASED, URHO3D_HANDLER(UIResearchDialog, HandleOK));
-  SubscribeToEvent(buttonCancel, E_RELEASED, URHO3D_HANDLER(UIResearchDialog, HandleCancel));
-  SubscribeToEvent(buttonClose, E_RELEASED, URHO3D_HANDLER(UIResearchDialog, HandleCancel));
-  SubscribeToEvent(searchWidget, E_TEXTCHANGED, URHO3D_HANDLER(UIResearchDialog, HandleSearchChanged));
-  SubscribeToEvent(showDiscoveredWidget, E_TOGGLED, [this](StringHash eventType, VariantMap& eventData) { FilterDiscoveries(); });
-  SubscribeToEvent(showResearchingWidget, E_TOGGLED, [this](StringHash eventType, VariantMap& eventData) { FilterDiscoveries(); });
-  SubscribeToEvent(showLockedWidget, E_TOGGLED, [this](StringHash eventType, VariantMap& eventData) { FilterDiscoveries(); });
-
-  AddRef();
-}
-
-UIResearchDialog::~UIResearchDialog()
-{
-  if (window)
-    window->Remove();
-}
-
-void UIResearchDialog::RegisterObject(Context* context)
-{
-  context->RegisterFactory<UIResearchDialog>();
-}
-
-SharedPtr<UIElement> UIResearchDialog::CreateDiscoveryWidget(const cryptonote::discovery_t &d)
-{
-  auto* cache = context_->GetSubsystem<ResourceCache>();
-  auto* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
-
-  SharedPtr<UIElement> mainLayout(new UIElement(context_));
-  mainLayout->SetStyleAuto(style);
-  mainLayout->SetLayout(LM_HORIZONTAL, 6, IntRect(8, 8, 8, 8));
-
-  SharedPtr<UIElement> leftColumnLayout(new UIElement(context_));
-  leftColumnLayout->SetStyleAuto(style);
-  leftColumnLayout->SetLayout(LM_VERTICAL, 6, IntRect(0, 2, 0, 2));
-  mainLayout->AddChild(leftColumnLayout);
+  const cryptonote::discovery_t &d = item->discovery;
 
   std::string status;
   enum { state_discovered, state_researching, state_locked } state;
@@ -269,118 +58,145 @@ SharedPtr<UIElement> UIResearchDialog::CreateDiscoveryWidget(const cryptonote::d
   else
     state = state_locked;
 
-  Color color;
+  TBColor color;
   switch (state)
   {
     case state_discovered:
-      status = "Discovered by " + game->get_player_name(d.discoverer);
-      color = Color(.5f, .9f, .8f);
+      status = "Discovered by " + item->dialog->game->get_player_name(d.discoverer);
+      color = TBColor(128, 230, 204, 255);
       break;
     case state_researching:
-      status = "Researching, current difficulty " + cryptonote::print_money(cc::get_research_age_adjusted_difficulty(d.difficulty, game->top_height - d.research_start_height, d.budget));
-      color = Color(.85f, .85f, .85f);
+      status = "Researching, current difficulty " + cryptonote::print_money(cc::get_research_age_adjusted_difficulty(d.difficulty, item->dialog->game->top_height - d.research_start_height, d.budget));
+      color = TBColor(255, 255, 255, 255);
       break;
     case state_locked:
-      status = "Locked (requires " + boost::join(d.prerequisites | boost::adaptors::transformed([this](uint32_t discovery){return discoveries[discovery].name;}), " ") + ")";
-      color = Color(.5f, .5f, .5f);
+      status = "Locked (requires " + boost::join(d.prerequisites | boost::adaptors::transformed([this, item](uint32_t discovery){return item->dialog->discoveries[discovery].name;}), " ") + ")";
+      color = TBColor(128, 128, 128, 255);
       break;
   }
 
-  SharedPtr<Text> text;
-  text = AddText(leftColumnLayout, d.name.c_str(), 1.5f);
-  text->SetColor(color);
-  text = AddText(leftColumnLayout, status.c_str());
-  text->SetTextEffect(TE_ITALIC);
-  text->SetColor(color);
-  text = AddText(leftColumnLayout, d.desc.c_str());
-  text->SetColor(color);
+  TBTextField *nameWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("name"));
+  nameWidget->SetText(d.name.c_str());
+  nameWidget->SetTextColor(color);
+  TBTextField *statusWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("status"));
+  statusWidget->SetText(status.c_str());
+  statusWidget->SetTextColor(color);
+  statusWidget->SetItalic(true);
+  TBTextField *descWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("desc"));
+  descWidget->SetText(d.desc.c_str());
+  descWidget->SetTextColor(color);
 
+  TBLayout *researching = GetWidgetByIDAndType<TBLayout>(TBIDC("researching"));
   if (state == state_researching)
   {
-    SharedPtr<UIElement> rightColumnLayout(new UIElement(context_));
-    rightColumnLayout->SetStyleAuto(style);
-    rightColumnLayout->SetLayout(LM_VERTICAL, 6, IntRect(0, 2, 0, 2));
-    mainLayout->AddChild(rightColumnLayout);
-
-    SharedPtr<LineEdit> amountWidget(new LineEdit(context_));
-    amountWidget->SetStyleAuto(style);
-    rightColumnLayout->SetMaxWidth(96);
-    rightColumnLayout->AddChild(amountWidget);
-
-    SharedPtr<Text> researchChanceWidget = AddText(rightColumnLayout, "0% chance");
-    researchChanceWidget->SetTextAlignment(HA_CENTER);
-
-    SharedPtr<Button> researchButton(new Button(context_));
-    researchButton->SetStyleAuto(style);
-    rightColumnLayout->AddChild(researchButton);
-    AddText(researchButton, "Research")->SetAlignment(HA_CENTER, VA_CENTER);
-
-    SubscribeToEvent(researchButton, E_RELEASED, [this, d, amountWidget](StringHash eventType, VariantMap& eventData) {
-      uint64_t amount;
-      if (!cryptonote::parse_amount(amount, amountWidget->GetText().CString()))
-      {
-        new MessageBox(context_, "Invalid amount");
-        return;
-      }
-      if (amount < MIN_RESEARCH_AMOUNT)
-      {
-        new MessageBox(context_, ("Amount too low, minimum is " + cryptonote::print_money(MIN_RESEARCH_AMOUNT)).c_str());
-        return;
-      }
-      VariantMap newEventData;
-      newEventData[ResearchFund::P_DISCOVERY] = d.discovery;
-      newEventData[ResearchFund::P_AMOUNT] = (unsigned long long)amount;
-      SendEvent(E_RESEARCH_FUND, newEventData);
-      new MessageBox(context_, (cryptonote::print_money(amount) + " paid for research\nResults will be known after next block is mined").c_str(), "Info");
-    });
-
-    SubscribeToEvent(amountWidget, E_TEXTCHANGED, [this, d, amountWidget, researchChanceWidget](StringHash eventType, VariantMap& eventData) {
-      uint64_t amount;
-      if (!cryptonote::parse_amount(amount, amountWidget->GetText().CString()))
-      {
-        researchChanceWidget->SetText("0% chance");
-        return;
-      }
-      if (amount < MIN_RESEARCH_AMOUNT)
-      {
-        researchChanceWidget->SetText("0% chance");
-        return;
-      }
-      const uint32_t chance = cc::get_discovery_chance_scaled(amount, d.budget, d.difficulty, research_bonus, d.research_start_height, game->top_height + 1);
-      if (chance == 0)
-      {
-        researchChanceWidget->SetText("0% chance");
-        return;
-      }
-      char s[32];
-      snprintf(s, sizeof(s), "%.3f%%", 100.0f * chance / RESEARCH_CHANCE_BASE_SCALE);
-      researchChanceWidget->SetText(s);
-    });
+    researching->SetVisibility(WIDGET_VISIBILITY_VISIBLE);
   }
-
-  mainLayout->SetMaxHeight(mainLayout->GetHeight());
-  return mainLayout;
+  else
+    researching->SetVisibility(WIDGET_VISIBILITY_INVISIBLE);
 }
 
-void UIResearchDialog::UpdateDiscoveriesList()
+bool UIResearchDialog::DiscoveryWidget::OnEvent(const tb::TBWidgetEvent &ev)
 {
-  auto* cache = context_->GetSubsystem<ResourceCache>();
-  auto* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
-
-  discoveriesListWidget->RemoveAllItems();
-
-  for (const auto &d: discoveries)
+  const UIResearchDialog::DiscoveryItem *item = m_source->GetItem(m_index);
+  const cryptonote::discovery_t &d = item->discovery;
+  if (ev.type == EVENT_TYPE_CHANGED && ev.target->GetID() == TBIDC("amount"))
   {
-    discoveriesListWidget->AddItem(CreateDiscoveryWidget(d));
+    TBEditField *ef = TBSafeCast<TBEditField>(ev.target);
+    TBTextField *researchChanceWidget = GetWidgetByIDAndType<TBTextField>("chance");
+    uint64_t amount;
+    if (!cryptonote::parse_amount(amount, ef->GetText().CStr()))
+    {
+      researchChanceWidget->SetText("0% chance");
+      return true;
+    }
+    if (amount < MIN_RESEARCH_AMOUNT)
+    {
+      researchChanceWidget->SetText("0% chance");
+      return true;
+    }
+    const uint32_t chance = cc::get_discovery_chance_scaled(amount, d.budget, d.difficulty, item->dialog->research_bonus, d.research_start_height, item->dialog->game->top_height + 1);
+    if (chance == 0)
+    {
+      researchChanceWidget->SetText("0% chance");
+      return true;
+    }
+    char s[32];
+    snprintf(s, sizeof(s), "%.3f%%", 100.0f * chance / RESEARCH_CHANCE_BASE_SCALE);
+    researchChanceWidget->SetText(s);
+    return true;
   }
+  else if (ev.type == EVENT_TYPE_CLICK && ev.target->GetID() == TBIDC("research"))
+  {
+    TBEditField *ef = TBSafeCast<TBEditField>(ev.target);
+    uint64_t amount;
+    if (!cryptonote::parse_amount(amount, ef->GetText().CStr()))
+    {
+      new MessageBox(item->dialog->context_, "Invalid amount");
+      return true;
+    }
+    if (amount < MIN_RESEARCH_AMOUNT)
+    {
+      new MessageBox(item->dialog->context_, ("Amount too low, minimum is " + cryptonote::print_money(MIN_RESEARCH_AMOUNT)).c_str());
+      return true;
+    }
+    VariantMap newEventData;
+    newEventData[ResearchFund::P_DISCOVERY] = d.discovery;
+    newEventData[ResearchFund::P_AMOUNT] = (unsigned long long)amount;
+    item->dialog->SendEvent(E_RESEARCH_FUND, newEventData);
+    new MessageBox(item->dialog->context_, (cryptonote::print_money(amount) + " paid for research\nResults will be known after next block is mined").c_str(), "Info");
+    return true;
+  }
+  return TBLayout::OnEvent(ev);
+}
+
+TBWidget *UIResearchDialog::DiscoverySource::CreateItemWidget(int index, TBSelectItemViewer *viewer)
+{
+  DiscoveryItem *item = GetItem(index);
+  return new UIResearchDialog::DiscoveryWidget(item, this, viewer, index);
+}
+
+UIResearchDialog::UIResearchDialog(Context *ctx, const GameState *game):
+  UITBWindow(ctx, "cc/research.tb.txt"),
+  game(game),
+  research_bonus(0)
+{
+  auto* graphics = GetSubsystem<Graphics>();
+  TBRect rect;
+  rect.w = graphics->GetWidth() * .9f;
+  rect.h = graphics->GetHeight() * .9f;
+  rect.x = graphics->GetWidth() * .05f;
+  rect.y = graphics->GetHeight() * .05f;
+  SetRect(rect);
+
+  balanceWidget = GetWidgetByIDAndType<TBTextField>("balance");
+  researchBonusWidget = GetWidgetByIDAndType<TBTextField>("research-bonus");
+  numDiscoveriesWidget = GetWidgetByIDAndType<TBTextField>("num-discoveries");
+  searchWidget = GetWidgetByIDAndType<TBEditField>("search");
+  discoveriesListWidget = GetWidgetByIDAndType<TBSelectList>("discoveries");
+  showDiscoveredWidget = GetWidgetByIDAndType<TBCheckBox>("show-discovered");
+  showResearchingWidget = GetWidgetByIDAndType<TBCheckBox>("show-researching");
+  showLockedWidget = GetWidgetByIDAndType<TBCheckBox>("show-locked");
+
+  discoveriesListWidget->SetSource(&discoverySource);
+
+  SubscribeToEvent(this, E_TB_WIDGET_EVENT, URHO3D_HANDLER(UIResearchDialog, HandleTBMessage));
+  SubscribeToEvent(this, E_TB_WINDOW_CLOSED, URHO3D_HANDLER(UIResearchDialog, HandleClose));
+
   FilterDiscoveries();
 }
 
+UIResearchDialog::~UIResearchDialog()
+{
+  discoveriesListWidget->SetSource(NULL);
+}
+
+void UIResearchDialog::RegisterObject(Context* context)
+{
+  context->RegisterFactory<UIResearchDialog>();
+}
+
 void UIResearchDialog::Refresh(const std::shared_ptr<GameWallet> &w)
 {
-  auto* cache = context_->GetSubsystem<ResourceCache>();
-  auto* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
-
   discoveries.clear();
   if (!w->get_cc_discoveries(discoveries))
     return;
@@ -394,7 +210,7 @@ void UIResearchDialog::Refresh(const std::shared_ptr<GameWallet> &w)
       active_research_buildings.push_back(std::make_tuple(flag->x1 - flag->x0 + 1, flag->y1 - flag->y0 + 1, flag->economic_power));
   }
   research_bonus = cc::get_research_bonus(active_research_buildings);
-  researchBonusWidget->SetText(String(research_bonus) + "%");
+  researchBonusWidget->SetText((String(research_bonus) + "%").CString());
 
   std::map<uint32_t, uint32_t> numDiscovered; 
   uint32_t totalDiscovered = 0;
@@ -409,9 +225,9 @@ void UIResearchDialog::Refresh(const std::shared_ptr<GameWallet> &w)
   String s = String(numDiscovered[game->playerState.id]) + "/" + String(totalDiscovered);
   if (numDiscovered[game->playerState.id] > 0)
     s += " (" + get_place_string(std::distance(numDiscovered.begin(), numDiscovered.find(game->playerState.id))) + " place)";
-  numDiscoveriesWidget->SetText(s);
+  numDiscoveriesWidget->SetText(s.CString());
 
-  UpdateDiscoveriesList();
+  FilterDiscoveries();
 }
 
 
@@ -430,63 +246,68 @@ void UIResearchDialog::CancelUI()
   SendEvent(E_RESEARCH_CANCELLED, newEventData);
 
   // Self destruct
-  ReleaseRef();
+  Close();
 }
 
 void UIResearchDialog::FilterDiscoveries()
 {
-  const char *search = searchWidget->GetText().CString();
-  const String search_string(search ? search : "");
-
-  const unsigned int n_elements = discoveriesListWidget->GetNumItems();
+  discoverySource.DeleteAllItems();
+  const size_t n_elements = discoveries.size();
   for (unsigned int n = 0; n < n_elements; ++n)
   {
-    bool visible = true;
     const cryptonote::discovery_t &d = discoveries[n];
+    bool visible = false;
     if (d.research_start_height > 0 || d.prerequisites.empty())
     {
       if (d.discoverer)
-        visible = showDiscoveredWidget->IsChecked();
+        visible = showDiscoveredWidget->GetValue();
       else
-        visible = showResearchingWidget->IsChecked();
+        visible = showResearchingWidget->GetValue();
     }
     else
-      visible = showLockedWidget->IsChecked();
+      visible = showLockedWidget->GetValue();
 
-    UIElement *e = discoveriesListWidget->GetItem(n);
-    if (visible && search && *search)
-    {
-      visible = false;
-      PODVector<UIElement*> dest;
-      e->GetChildren(dest, true);
-      for (const auto &w: dest)
-      {
-        const Text *text = dynamic_cast<const Text*>(w);
-        if (text && text->GetText().Contains(search_string, false))
-        {
-          visible = true;
-          break;
-        }
-      }
-    }
-    e->SetVisible(visible);
+    if (visible)
+      discoverySource.AddItem(new DiscoveryItem(this, d));
   }
 }
 
-void UIResearchDialog::HandleSearchChanged(StringHash eventType, VariantMap& eventData)
+void UIResearchDialog::HandleFilterChanged(StringHash eventType, VariantMap& eventData)
 {
   FilterDiscoveries();
 }
 
-void UIResearchDialog::HandleOK(StringHash eventType, VariantMap& eventData)
+void UIResearchDialog::HandleSearchChanged(StringHash eventType, VariantMap& eventData)
 {
+  discoveriesListWidget->SetFilter(searchWidget->GetText());
 }
 
-void UIResearchDialog::HandleCancel(StringHash eventType, VariantMap& eventData)
+void UIResearchDialog::HandleTBMessage(StringHash eventType, VariantMap& eventData)
+{
+#define CONNECT(name, function) do { if (ev->target->GetID() == TBIDC(name)) function(eventType, eventData); } while(0)
+  using namespace TBWidgetEventNamespace;
+
+  TBWidgetEvent *ev = (TBWidgetEvent*)eventData[P_WIDGET_EVENT].GetVoidPtr();
+  if (ev->type == EVENT_TYPE_CLICK)
+  {
+  }
+  else if (ev->type == EVENT_TYPE_CHANGED)
+  {
+    CONNECT("show-discovered", HandleFilterChanged);
+    CONNECT("show-researching", HandleFilterChanged);
+    CONNECT("show-locked", HandleFilterChanged);
+
+    CONNECT("search", HandleSearchChanged);
+  }
+
+#undef CONNECT
+}
+
+void UIResearchDialog::HandleClose(StringHash eventType, VariantMap& eventData)
 {
   VariantMap& newEventData = GetEventDataMap();
   SendEvent(E_RESEARCH_CANCELLED, newEventData);
 
   // Self destruct
-  ReleaseRef();
+  Close();
 }
diff --git a/src/game/ui-research.h b/src/game/ui-research.h
index 81d6141b6..4e6d5468f 100644
--- a/src/game/ui-research.h
+++ b/src/game/ui-research.h
@@ -6,18 +6,22 @@
 #include <Urho3D/Container/Str.h>
 #include <Urho3D/Container/Ptr.h>
 #include <Urho3D/Math/StringHash.h>
+#include <tb/tb_select_item.h>
 #include "cryptonote_basic/cc_command_defs.h"
 #include "rpc/core_rpc_server_commands_defs.h"
+#include "ui-tb-window.h"
 
 namespace Urho3D
 {
-  class UIElement;
   class Context;
-  class Window;
-  class LineEdit;
-  class ListView;
-  class CheckBox;
-  class Text;
+}
+
+namespace tb
+{
+  class TBEditField;
+  class TBTextField;
+  class TBSelectList;
+  class TBCheckBox;
 }
 
 class GameState;
@@ -26,7 +30,7 @@ class GameWallet;
 URHO3D_EVENT(E_RESEARCH_FUND, ResearchFund) { URHO3D_PARAM(P_DISCOVERY, Discovery); URHO3D_PARAM(P_AMOUNT, Amount); }
 URHO3D_EVENT(E_RESEARCH_CANCELLED, ResearchCancelled) {}
 
-class UIResearchDialog: public Urho3D::Object
+class UIResearchDialog: public UITBWindow
 {
   URHO3D_OBJECT(UIResearchDialog, Object);
 
@@ -40,31 +44,58 @@ public:
   void CancelUI();
 
 private:
-  void UpdateDiscoveriesList();
   void Refresh(const std::shared_ptr<GameWallet> &w);
   Urho3D::SharedPtr<Urho3D::UIElement> CreateDiscoveryWidget(const cryptonote::discovery_t &d);
   void FilterDiscoveries();
 
 private:
-  void HandleOK(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
-  void HandleCancel(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
   void HandleSearchChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
+  void HandleFilterChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
+  void HandleTBMessage(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
+  void HandleClose(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
 
 private:
   const GameState *game;
-  Urho3D::SharedPtr<Urho3D::Window> window;
-  Urho3D::SharedPtr<Urho3D::Text> balanceWidget;
-  Urho3D::SharedPtr<Urho3D::Text> researchBonusWidget;
-  Urho3D::SharedPtr<Urho3D::Text> numDiscoveriesWidget;
-  Urho3D::SharedPtr<Urho3D::LineEdit> searchWidget;
-  Urho3D::SharedPtr<Urho3D::ListView> discoveriesListWidget;
-  Urho3D::SharedPtr<Urho3D::CheckBox> showDiscoveredWidget;
-  Urho3D::SharedPtr<Urho3D::CheckBox> showResearchingWidget;
-  Urho3D::SharedPtr<Urho3D::CheckBox> showLockedWidget;
+  tb::TBTextField *balanceWidget;
+  tb::TBTextField *researchBonusWidget;
+  tb::TBTextField *numDiscoveriesWidget;
+  tb::TBEditField *searchWidget;
+  tb::TBSelectList *discoveriesListWidget;
+  tb::TBCheckBox *showDiscoveredWidget;
+  tb::TBCheckBox *showResearchingWidget;
+  tb::TBCheckBox *showLockedWidget;
 
   std::vector<cryptonote::discovery_t> discoveries;
   uint32_t research_bonus;
   std::string last_refresh_stop_hash;
+
+  class DiscoveryItem: public tb::TBGenericStringItem
+  {
+  public:
+    DiscoveryItem(UIResearchDialog *dialog, const cryptonote::discovery_t &discovery): TBGenericStringItem(discovery.name.c_str()), dialog(dialog), discovery(discovery) {}
+    UIResearchDialog *dialog;
+    cryptonote::discovery_t discovery;
+  };
+
+  class DiscoverySource: public tb::TBSelectItemSourceList<DiscoveryItem>
+  {
+  public:
+    virtual bool Filter(int index, const char *filter) { return true; }
+    virtual tb::TBWidget *CreateItemWidget(int index, tb::TBSelectItemViewer *viewer);
+  };
+
+  class DiscoveryWidget: public tb::TBLayout
+  {
+  public:
+    DiscoveryWidget(DiscoveryItem *item, DiscoverySource *source, tb::TBSelectItemViewer *source_viewer, int index);
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev);
+  private:
+    DiscoverySource *m_source;
+    tb::TBSelectItemViewer *m_source_viewer;
+    int m_index;
+  };
+
+  DiscoverySource discoverySource;
 };
 
 #endif