mirror of
https://github.com/xenia-project/elemental-forms.git
synced 2026-01-31 01:25:17 +01:00
876 lines
33 KiB
C++
876 lines
33 KiB
C++
/**
|
|
******************************************************************************
|
|
* Elemental Forms : a lightweight user interface framework *
|
|
******************************************************************************
|
|
* ©2015 Ben Vanik. All rights reserved. Released under the BSD license. *
|
|
* Portions ©2011-2015 Emil Segerås: https://github.com/fruxo/turbobadger *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
|
|
// TODO(benvanik): remove when real testing implemented.
|
|
#include "el/testing/testing.h"
|
|
|
|
#include "el/animation_manager.h"
|
|
#include "el/element_animation.h"
|
|
#include "el/element_animation_manager.h"
|
|
#include "el/elemental_forms.h"
|
|
#include "el/elements.h"
|
|
#include "el/io/file_manager.h"
|
|
#include "el/io/memory_file_system.h"
|
|
#include "el/io/posix_file_system.h"
|
|
#include "el/io/win32_res_file_system.h"
|
|
#include "el/parsing/parse_node.h"
|
|
#include "el/text/font_manager.h"
|
|
#include "el/text/font_renderer.h"
|
|
#include "el/text/utf8.h"
|
|
#include "el/util/debug.h"
|
|
#include "el/util/metrics.h"
|
|
#include "el/util/string.h"
|
|
#include "el/util/string_builder.h"
|
|
#include "el/util/string_table.h"
|
|
#include "testbed/list_window.h"
|
|
#include "testbed/resource_edit_window.h"
|
|
#include "testbed/scratch/code_text_box.h"
|
|
#include "testbed/testbed_application.h"
|
|
|
|
namespace testbed {
|
|
|
|
using namespace el;
|
|
using el::graphics::Renderer;
|
|
using el::parsing::ParseNode;
|
|
|
|
AdvancedItemSource advanced_source;
|
|
GenericStringItemSource name_source;
|
|
GenericStringItemSource popup_menu_source;
|
|
|
|
static TestbedApplication* application;
|
|
|
|
int fps = 0;
|
|
uint32_t frame_counter_total = 0;
|
|
uint32_t frame_counter = 0;
|
|
uint64_t frame_counter_reset_time = 0;
|
|
|
|
const char* girl_names[] = {
|
|
"Maja", "Alice", "Julia", "Linnéa", "Wilma", "Ella",
|
|
"Elsa", "Emma", "Alva", "Olivia", "Molly", "Ebba",
|
|
"Klara", "Nellie", "Agnes", "Isabelle", "Ida", "Elin",
|
|
"Ellen", "Moa", "Emilia", "Nova", "Alma", "Saga",
|
|
"Amanda", "Isabella", "Lilly", "Alicia", "Astrid", "Matilda",
|
|
"Tuva", "Tilde", "Stella", "Felicia", "Elvira", "Tyra",
|
|
"Hanna", "Sara", "Vera", "Thea", "Freja", "Lova",
|
|
"Selma", "Meja", "Signe", "Ester", "Lovisa", "Ellie",
|
|
"Lea", "Tilda", "Tindra", "Sofia", "Nora", "Nathalie",
|
|
"Leia", "Filippa", "Siri", "Emelie", "Inez", "Edith",
|
|
"Stina", "Liv", "Lisa", "Linn", "Tove", "Emmy",
|
|
"Livia", "Jasmine", "Evelina", "Cornelia", "Märta", "Svea",
|
|
"Ingrid", "My", "Rebecca", "Joline", "Mira", "Ronja",
|
|
"Hilda", "Melissa", "Anna", "Frida", "Maria", "Iris",
|
|
"Josefine", "Elise", "Elina", "Greta", "Vilda", "Minna",
|
|
"Lina", "Hedda", "Nicole", "Kajsa", "Majken", "Sofie",
|
|
"Annie", "Juni", "Novalie", "Hedvig", 0};
|
|
const char* boy_names[] = {
|
|
"Oscar", "William", "Lucas", "Elias", "Alexander", "Hugo",
|
|
"Oliver", "Theo", "Liam", "Leo", "Viktor", "Erik",
|
|
"Emil", "Isak", "Axel", "Filip", "Anton", "Gustav",
|
|
"Edvin", "Vincent", "Arvid", "Albin", "Ludvig", "Melvin",
|
|
"Noah", "Charlie", "Max", "Elliot", "Viggo", "Alvin",
|
|
"Alfred", "Theodor", "Adam", "Olle", "Wilmer", "Benjamin",
|
|
"Simon", "Nils", "Noel", "Jacob", "Leon", "Rasmus",
|
|
"Kevin", "Linus", "Casper", "Gabriel", "Jonathan", "Milo",
|
|
"Melker", "Felix", "Love", "Ville", "Sebastian", "Sixten",
|
|
"Carl", "Malte", "Neo", "David", "Joel", "Adrian",
|
|
"Valter", "Josef", "Jack", "Hampus", "Samuel", "Mohammed",
|
|
"Alex", "Tim", "Daniel", "Vilgot", "Wilhelm", "Harry",
|
|
"Milton", "Maximilian", "Robin", "Sigge", "Måns", "Eddie",
|
|
"Elton", "Vidar", "Hjalmar", "Loke", "Elis", "August",
|
|
"John", "Hannes", "Sam", "Frank", "Svante", "Marcus",
|
|
"Mio", "Otto", "Ali", "Johannes", "Fabian", "Ebbe",
|
|
"Aron", "Julian", "Elvin", "Ivar", 0};
|
|
|
|
bool TestbedApplication::Init() {
|
|
// Block new animations during Init.
|
|
AnimationBlocker anim_blocker;
|
|
|
|
// Run unit tests
|
|
int num_failed_tests = TBRunTests();
|
|
|
|
// ListBox and DropDownButton elements have a default item source that
|
|
// are fed with any items
|
|
// specified in the resource files. But it is also possible to set any source
|
|
// which can save memory
|
|
// and improve performance. Then you don't have to populate each instance with
|
|
// its own set of items,
|
|
// for elements that occur many times in a UI, always with the same items.
|
|
// Here we prepare the name source, that is used in a few places.
|
|
for (int i = 0; boy_names[i]; i++)
|
|
advanced_source.push_back(std::make_unique<AdvancedItem>(
|
|
boy_names[i++], TBIDC("boy_item"), true));
|
|
for (int i = 0; girl_names[i]; i++)
|
|
advanced_source.push_back(std::make_unique<AdvancedItem>(
|
|
girl_names[i++], TBIDC("girl_item"), false));
|
|
for (int i = 0; girl_names[i]; i++)
|
|
name_source.push_back(std::make_unique<GenericStringItem>(
|
|
girl_names[i++], TBIDC("girl_item")));
|
|
for (int i = 0; boy_names[i]; i++)
|
|
name_source.push_back(
|
|
std::make_unique<GenericStringItem>(boy_names[i++], TBIDC("boy_item")));
|
|
advanced_source.set_sort(Sort::kAscending);
|
|
name_source.set_sort(Sort::kAscending);
|
|
|
|
// Prepare a source with submenus (with eternal recursion) so we can test sub
|
|
// menu support.
|
|
popup_menu_source.push_back(
|
|
std::make_unique<GenericStringItem>("Option 1", TBIDC("opt 1")));
|
|
popup_menu_source.push_back(
|
|
std::make_unique<GenericStringItem>("Option 2", TBIDC("opt 2")));
|
|
popup_menu_source.push_back(std::make_unique<GenericStringItem>("-"));
|
|
popup_menu_source.push_back(
|
|
std::make_unique<GenericStringItem>("Same submenu", &popup_menu_source));
|
|
popup_menu_source.push_back(
|
|
std::make_unique<GenericStringItem>("Long submenu", &name_source));
|
|
// Give the first item a skin image
|
|
popup_menu_source[0]->set_icon(TBIDC("Icon16"));
|
|
|
|
new MainWindow();
|
|
|
|
new EditWindow();
|
|
|
|
new ListWindow(&name_source);
|
|
|
|
new AdvancedListWindow(&advanced_source);
|
|
|
|
new TabContainerWindow();
|
|
|
|
if (num_failed_tests) {
|
|
MessageWindow* msg_win = new MessageWindow(GetRoot(), TBIDC(""));
|
|
msg_win->Show(
|
|
"Testing results",
|
|
el::util::format_string(
|
|
"There is %d failed tests!\nCheck the output for details.",
|
|
num_failed_tests));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TestbedApplication::Run() { m_backend->Run(); }
|
|
|
|
void TestbedApplication::ShutDown() {
|
|
delete m_backend;
|
|
m_backend = nullptr;
|
|
}
|
|
|
|
void TestbedApplication::Process() {
|
|
AnimationManager::Update();
|
|
GetRoot()->InvokeProcessStates();
|
|
GetRoot()->InvokeProcess();
|
|
}
|
|
|
|
void TestbedApplication::RenderFrame(int window_w, int window_h) {
|
|
// Render
|
|
Renderer::get()->BeginPaint(window_w, window_h);
|
|
GetRoot()->InvokePaint(Element::PaintProps());
|
|
|
|
frame_counter++;
|
|
frame_counter_total++;
|
|
|
|
// Update the FPS counter
|
|
uint64_t time = util::GetTimeMS();
|
|
if (time > frame_counter_reset_time + 1000) {
|
|
fps = (int)((frame_counter / double(time - frame_counter_reset_time)) *
|
|
1000.0);
|
|
frame_counter_reset_time = time;
|
|
frame_counter = 0;
|
|
}
|
|
|
|
// Draw FPS
|
|
ElementValue* continuous_repaint_val =
|
|
ElementValueGroup::get()->GetValue(TBIDC("continous-repaint"));
|
|
bool continuous_repaint =
|
|
continuous_repaint_val ? !!continuous_repaint_val->as_integer() : 0;
|
|
|
|
std::string str;
|
|
if (continuous_repaint) {
|
|
str = el::util::format_string("FPS: %d Frame %d", fps, frame_counter_total);
|
|
} else {
|
|
str = el::util::format_string("Frame %d", frame_counter_total);
|
|
}
|
|
GetRoot()->computed_font()->DrawString(5, 5, Color(255, 255, 255), str);
|
|
|
|
Renderer::get()->EndPaint();
|
|
|
|
// If we want continous updates or got animations running, reinvalidate
|
|
// immediately
|
|
if (continuous_repaint || AnimationManager::has_running_animations()) {
|
|
GetRoot()->Invalidate();
|
|
}
|
|
}
|
|
|
|
int app_main() {
|
|
application = new TestbedApplication();
|
|
|
|
ApplicationBackend* application_backend =
|
|
ApplicationBackend::Create(application, 1280, 700, "Demo");
|
|
if (!application_backend) return 1;
|
|
|
|
el::Initialize(application_backend->GetRenderer());
|
|
CodeTextBox::RegisterInflater();
|
|
|
|
//el::io::FileManager::RegisterFileSystem(
|
|
// std::make_unique<el::io::PosixFileSystem>("./resources"));
|
|
el::io::FileManager::RegisterFileSystem(
|
|
std::make_unique<el::io::PosixFileSystem>("./testbed/resources"));
|
|
el::io::FileManager::RegisterFileSystem(
|
|
std::make_unique<el::io::Win32ResFileSystem>("IDR_default_resources_"));
|
|
el::io::FileManager::RegisterFileSystem(
|
|
std::make_unique<el::io::Win32ResFileSystem>("IDR_testbed_resources_"));
|
|
|
|
util::StringTable::get()->Load("default_language/language_en.tb.txt");
|
|
|
|
// Load the default skin, and override skin that contains the graphics
|
|
// specific to the demo.
|
|
Skin::get()->Load("default_skin/skin.tb.txt");
|
|
Skin::get()->Load("skin/skin.tb.txt");
|
|
|
|
// Register font renderers.
|
|
#ifdef EL_FONT_RENDERER_TBBF
|
|
void register_tbbf_font_renderer();
|
|
register_tbbf_font_renderer();
|
|
#endif
|
|
#ifdef EL_FONT_RENDERER_STB
|
|
void register_stb_font_renderer();
|
|
register_stb_font_renderer();
|
|
#endif
|
|
#ifdef EL_FONT_RENDERER_FREETYPE
|
|
void register_freetype_font_renderer();
|
|
register_freetype_font_renderer();
|
|
#endif
|
|
auto font_manager = text::FontManager::get();
|
|
|
|
// Add fonts we can use to the font manager.
|
|
#if defined(EL_FONT_RENDERER_STB) || defined(EL_FONT_RENDERER_FREETYPE)
|
|
font_manager->AddFontInfo("fonts/vera.ttf", "Vera");
|
|
font_manager->AddFontInfo("fonts/vera-mono.ttf", "Vera Mono");
|
|
font_manager->AddFontInfo("fonts/vera-mono-bold.ttf", "Vera Mono Bold");
|
|
font_manager->AddFontInfo("fonts/vera-mono-bold-italic.ttf",
|
|
"Vera Mono Bold Italic");
|
|
font_manager->AddFontInfo("fonts/vera-mono-italic.ttf", "Vera Mono Italic");
|
|
font_manager->AddFontInfo("fonts/ProggyClean.ttf", "ProggyClean");
|
|
#endif
|
|
#ifdef EL_FONT_RENDERER_TBBF
|
|
font_manager->AddFontInfo("fonts/segoe_white_with_shadow.tb.txt", "Segoe");
|
|
font_manager->AddFontInfo("fonts/neon.tb.txt", "Neon");
|
|
font_manager->AddFontInfo("fonts/orangutang.tb.txt", "Orangutang");
|
|
font_manager->AddFontInfo("fonts/orange.tb.txt", "Orange");
|
|
#endif
|
|
|
|
// Set the default font description for elements to one of the fonts we just
|
|
// added
|
|
FontDescription fd;
|
|
#ifdef EL_FONT_RENDERER_TBBF
|
|
fd.set_id(TBIDC("Segoe"));
|
|
fd.set_size(Skin::get()->dimension_converter()->DpToPx(14));
|
|
#else
|
|
fd.set_id(TBIDC("ProggyClean"));
|
|
fd.set_size(Skin::get()->dimension_converter()->DpToPx(10));
|
|
#endif
|
|
font_manager->set_default_font_description(fd);
|
|
|
|
// Create the font now.
|
|
auto font =
|
|
font_manager->CreateFontFace(font_manager->default_font_description());
|
|
|
|
// Render some glyphs in one go now since we know we are going to use them. It
|
|
// would work fine
|
|
// without this since glyphs are rendered when needed, but with some extra
|
|
// updating of the glyph bitmap.
|
|
if (font)
|
|
font->RenderGlyphs(
|
|
" !\"#$%&'()*+,-./"
|
|
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
|
|
"abcdefghijklmnopqrstuvwxyz{|}~•·åäöÅÄÖ");
|
|
|
|
// Give the root element a background skin
|
|
application->GetRoot()->set_background_skin(TBIDC("background"));
|
|
|
|
application->Init();
|
|
application->Run();
|
|
application->ShutDown();
|
|
|
|
delete application;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// == DemoWindow ==============================================================
|
|
|
|
DemoWindow::DemoWindow() { application->GetRoot()->AddChild(this); }
|
|
|
|
bool DemoWindow::LoadResourceFile(const std::string& filename) {
|
|
// We could do ElementFactory::get()->LoadFile(this, filename) but we want
|
|
// some extra data we store under "WindowInfo", so read into node tree.
|
|
ParseNode node;
|
|
if (!node.ReadFile(filename)) return false;
|
|
LoadResource(node);
|
|
return true;
|
|
}
|
|
|
|
void DemoWindow::LoadResourceData(const char* data) {
|
|
// We could do ElementFactory::get()->LoadData(this, filename) but we want
|
|
// some extra data we store under "WindowInfo", so read into node tree.
|
|
ParseNode node;
|
|
node.ReadData(data);
|
|
LoadResource(node);
|
|
}
|
|
|
|
void DemoWindow::LoadResource(ParseNode& node) {
|
|
this->LoadNodeTree(&node);
|
|
|
|
// Get title from the WindowInfo section (or use "" if not specified)
|
|
set_text(node.GetValueString("WindowInfo>title", ""));
|
|
|
|
const Rect parent_rect(0, 0, parent()->rect().w, parent()->rect().h);
|
|
auto dc = Skin::get()->dimension_converter();
|
|
Rect window_rect = GetResizeToFitContentRect();
|
|
|
|
// Use specified size or adapt to the preferred content size.
|
|
ParseNode* tmp = node.GetNode("WindowInfo>size");
|
|
if (tmp && tmp->value().array_size() == 2) {
|
|
window_rect.w = dc->GetPxFromString(
|
|
tmp->value().as_array()->at(0)->as_string(), window_rect.w);
|
|
window_rect.h = dc->GetPxFromString(
|
|
tmp->value().as_array()->at(1)->as_string(), window_rect.h);
|
|
}
|
|
|
|
// Use the specified position or center in parent.
|
|
tmp = node.GetNode("WindowInfo>position");
|
|
if (tmp && tmp->value().array_size() == 2) {
|
|
window_rect.x = dc->GetPxFromString(
|
|
tmp->value().as_array()->at(0)->as_string(), window_rect.x);
|
|
window_rect.y = dc->GetPxFromString(
|
|
tmp->value().as_array()->at(1)->as_string(), window_rect.y);
|
|
} else
|
|
window_rect = window_rect.CenterIn(parent_rect);
|
|
|
|
// Make sure the window is inside the parent, and not larger.
|
|
window_rect = window_rect.MoveIn(parent_rect).Clip(parent_rect);
|
|
|
|
set_rect(window_rect);
|
|
|
|
// Ensure we have focus - now that we've filled the window with possible
|
|
// focusable
|
|
// elements. EnsureFocus was automatically called when the window was
|
|
// activated
|
|
// (by
|
|
// adding the window to the root), but then we had nothing to focus.
|
|
// Alternatively, we could add the window after setting it up properly.
|
|
EnsureFocus();
|
|
}
|
|
|
|
bool DemoWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kKeyDown && ev.special_key == SpecialKey::kEsc) {
|
|
// We could call Die() to fade away and die, but click the close button
|
|
// instead.
|
|
// That way the window has a chance of intercepting the close and f.ex ask
|
|
// if it really should be closed.
|
|
Event click_ev(EventType::kClick);
|
|
title_close_button_.InvokeEvent(click_ev);
|
|
return true;
|
|
}
|
|
return Window::OnEvent(ev);
|
|
}
|
|
|
|
// == EditWindow ==============================================================
|
|
|
|
EditWindow::EditWindow() { LoadResourceFile("test_textwindow.tb.txt"); }
|
|
|
|
void EditWindow::OnProcessStates() {
|
|
// Update the disabled state of undo/redo buttons, and caret info.
|
|
|
|
if (TextBox* edit = GetElementById<TextBox>(TBIDC("text_box"))) {
|
|
if (auto undo = GetElementById<Element>("undo"))
|
|
undo->set_state(Element::State::kDisabled, !edit->text_view()->CanUndo());
|
|
if (auto redo = GetElementById<Element>("redo"))
|
|
redo->set_state(Element::State::kDisabled, !edit->text_view()->CanRedo());
|
|
if (auto info = GetElementById<Label>(TBIDC("info"))) {
|
|
info->set_text_format("Caret ofs: %d",
|
|
edit->text_view()->caret.global_offset());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EditWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick) {
|
|
TextBox* edit = GetElementById<TextBox>(TBIDC("text_box"));
|
|
if (!edit) return false;
|
|
|
|
if (ev.target->id() == TBIDC("clear")) {
|
|
edit->set_text("");
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("undo")) {
|
|
edit->text_view()->Undo();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("redo")) {
|
|
edit->text_view()->Redo();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("menu")) {
|
|
static GenericStringItemSource source;
|
|
if (!source.size()) {
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Default font", TBIDC("default font")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Default font (larger)", TBIDC("large font")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"RGB font (Neon)", TBIDC("rgb font Neon")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"RGB font (Orangutang)", TBIDC("rgb font Orangutang")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"RGB font (Orange)", TBIDC("rgb font Orange")));
|
|
source.push_back(std::make_unique<GenericStringItem>("-"));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Glyph cache stresstest (CJK)", TBIDC("CJK")));
|
|
source.push_back(std::make_unique<GenericStringItem>("-"));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Toggle wrapping", TBIDC("toggle wrapping")));
|
|
source.push_back(std::make_unique<GenericStringItem>("-"));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Align left", TBIDC("align left")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Align center", TBIDC("align center")));
|
|
source.push_back(std::make_unique<GenericStringItem>(
|
|
"Align right", TBIDC("align right")));
|
|
}
|
|
|
|
if (MenuWindow* menu = new MenuWindow(ev.target, TBIDC("popup_menu")))
|
|
menu->Show(&source, PopupAlignment());
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("popup_menu")) {
|
|
if (ev.ref_id == TBIDC("default font"))
|
|
edit->set_font_description(FontDescription());
|
|
else if (ev.ref_id == TBIDC("large font")) {
|
|
auto fd = text::FontManager::get()->default_font_description();
|
|
fd.set_size(28);
|
|
edit->set_font_description(fd);
|
|
} else if (ev.ref_id == TBIDC("rgb font Neon")) {
|
|
FontDescription fd = edit->computed_font_description();
|
|
fd.set_id(TBIDC("Neon"));
|
|
edit->set_font_description(fd);
|
|
} else if (ev.ref_id == TBIDC("rgb font Orangutang")) {
|
|
FontDescription fd = edit->computed_font_description();
|
|
fd.set_id(TBIDC("Orangutang"));
|
|
edit->set_font_description(fd);
|
|
} else if (ev.ref_id == TBIDC("rgb font Orange")) {
|
|
FontDescription fd = edit->computed_font_description();
|
|
fd.set_id(TBIDC("Orange"));
|
|
edit->set_font_description(fd);
|
|
} else if (ev.ref_id == TBIDC("CJK")) {
|
|
util::StringBuilder buf;
|
|
for (int i = 0, cp = 0x4E00; cp <= 0x9FCC; ++cp, ++i) {
|
|
char utf8[8];
|
|
int len = text::utf8::encode(cp, utf8);
|
|
buf.Append(utf8, len);
|
|
if (i % 64 == 63) buf.Append("\n", 1);
|
|
}
|
|
edit->text_view()->set_text(buf.c_str(), buf.GetAppendPos());
|
|
} else if (ev.ref_id == TBIDC("toggle wrapping"))
|
|
edit->set_wrapping(!edit->is_wrapping());
|
|
else if (ev.ref_id == TBIDC("align left"))
|
|
edit->set_text_align(TextAlign::kLeft);
|
|
else if (ev.ref_id == TBIDC("align center"))
|
|
edit->set_text_align(TextAlign::kCenter);
|
|
else if (ev.ref_id == TBIDC("align right"))
|
|
edit->set_text_align(TextAlign::kRight);
|
|
return true;
|
|
}
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == LayoutWindow ============================================================
|
|
|
|
LayoutWindow::LayoutWindow(const std::string& filename) {
|
|
LoadResourceFile(filename);
|
|
}
|
|
|
|
bool LayoutWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kChanged &&
|
|
ev.target->id() == TBIDC("select position")) {
|
|
LayoutPosition pos = LayoutPosition::kCenter;
|
|
if (DropDownButton* select =
|
|
GetElementById<DropDownButton>(TBIDC("select position")))
|
|
pos = static_cast<LayoutPosition>(select->value());
|
|
for (int i = 0; i < 3; i++)
|
|
if (auto layout = GetElementById<LayoutBox>(i + 1))
|
|
layout->set_layout_position(pos);
|
|
return true;
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("toggle axis")) {
|
|
static Axis axis = Axis::kY;
|
|
for (int i = 0; i < 3; i++)
|
|
if (auto layout = GetElementById<LayoutBox>(i + 1))
|
|
layout->set_axis(axis);
|
|
axis = axis == Axis::kX ? Axis::kY : Axis::kX;
|
|
if (auto layout = GetElementById<LayoutBox>(TBIDC("switch_layout")))
|
|
layout->set_axis(axis);
|
|
ResizeToFitContent(ResizeFit::kCurrentOrNeeded);
|
|
return true;
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == TabContainerWindow
|
|
// ============================================================
|
|
|
|
TabContainerWindow::TabContainerWindow() {
|
|
LoadResourceFile("test_tabcontainer01.tb.txt");
|
|
}
|
|
|
|
bool TabContainerWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick && ev.target->id() == TBIDC("set_align")) {
|
|
if (TabContainer* tc = GetElementById<TabContainer>(TBIDC("tabcontainer")))
|
|
tc->set_alignment(static_cast<Align>(ev.target->data.as_integer()));
|
|
ResizeToFitContent(ResizeFit::kCurrentOrNeeded);
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("toggle_tab_axis")) {
|
|
static Axis axis = Axis::kX;
|
|
axis = axis == Axis::kX ? Axis::kY : Axis::kX;
|
|
if (TabContainer* tc =
|
|
GetElementById<TabContainer>(TBIDC("tabcontainer"))) {
|
|
for (Element* child = tc->tab_bar()->first_child(); child;
|
|
child = child->GetNext()) {
|
|
if (Button* button = util::SafeCast<Button>(child)) {
|
|
button->set_axis(axis);
|
|
}
|
|
}
|
|
}
|
|
ResizeToFitContent(ResizeFit::kCurrentOrNeeded);
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("start_spinner")) {
|
|
if (ProgressSpinner* spinner =
|
|
GetElementById<ProgressSpinner>(TBIDC("spinner")))
|
|
spinner->set_value(1);
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("stop_spinner")) {
|
|
if (ProgressSpinner* spinner =
|
|
GetElementById<ProgressSpinner>(TBIDC("spinner")))
|
|
spinner->set_value(0);
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == ConnectionWindow =========================================================
|
|
|
|
ConnectionWindow::ConnectionWindow() {
|
|
LoadResourceFile("test_connections.tb.txt");
|
|
}
|
|
|
|
bool ConnectionWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("reset-master-volume")) {
|
|
if (ElementValue* val =
|
|
ElementValueGroup::get()->GetValue(TBIDC("master-volume")))
|
|
val->set_integer(50);
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("reset-user-name")) {
|
|
if (ElementValue* val =
|
|
ElementValueGroup::get()->GetValue(TBIDC("user-name")))
|
|
val->set_text("");
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == ScrollContainerWindow ===================================================
|
|
|
|
ScrollContainerWindow::ScrollContainerWindow() {
|
|
LoadResourceFile("test_scrollcontainer.tb.txt");
|
|
|
|
if (DropDownButton* select =
|
|
GetElementById<DropDownButton>(TBIDC("name dropdown")))
|
|
select->set_source(&name_source);
|
|
|
|
if (DropDownButton* select =
|
|
GetElementById<DropDownButton>(TBIDC("advanced dropdown")))
|
|
select->set_source(&advanced_source);
|
|
}
|
|
|
|
bool ScrollContainerWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick) {
|
|
if (ev.target->id() == TBIDC("add img")) {
|
|
Button* button = util::SafeCast<Button>(ev.target);
|
|
IconBox* skin_image = new IconBox;
|
|
skin_image->set_background_skin(TBIDC("Icon16"));
|
|
button->content_root()->AddChild(skin_image, ElementZ::kBottom);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("new buttons")) {
|
|
for (int i = 0; i < ev.target->data.as_integer(); ++i) {
|
|
Button* button = new Button;
|
|
button->set_id(TBIDC("remove button"));
|
|
button->set_text_format("Remove %d", i);
|
|
ev.target->parent()->AddChild(button);
|
|
}
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("new buttons delayed")) {
|
|
for (int i = 0; i < ev.target->data.as_integer(); ++i) {
|
|
auto msg_data = std::make_unique<MessageData>();
|
|
msg_data->id1 = ev.target->parent()->id();
|
|
msg_data->v1.set_integer(i);
|
|
PostMessageDelayed(TBIDC("new button"), std::move(msg_data),
|
|
100 + i * 500);
|
|
}
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("remove button")) {
|
|
ev.target->parent()->RemoveChild(ev.target);
|
|
delete ev.target;
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("showpopupmenu1")) {
|
|
if (MenuWindow* menu = new MenuWindow(ev.target, TBIDC("popupmenu1")))
|
|
menu->Show(&popup_menu_source, PopupAlignment());
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("popupmenu1")) {
|
|
MessageWindow* msg_win = new MessageWindow(this, TBIDC("popup_dialog"));
|
|
msg_win->Show("Info",
|
|
el::util::format_string("Menu event received!\nref_id: %d",
|
|
(int)ev.ref_id));
|
|
return true;
|
|
}
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
void ScrollContainerWindow::OnMessageReceived(Message* msg) {
|
|
if (msg->message_id() == TBIDC("new button") && msg->data()) {
|
|
if (auto target = GetElementById<Element>(msg->data()->id1)) {
|
|
Button* button = new Button;
|
|
button->set_id(TBIDC("remove button"));
|
|
button->set_text(
|
|
el::util::format_string("Remove %d", msg->data()->v1.as_integer()));
|
|
target->AddChild(button);
|
|
}
|
|
}
|
|
}
|
|
|
|
// == ImageWindow =============================================================
|
|
|
|
ImageWindow::ImageWindow() { LoadResourceFile("test_image_widget.tb.txt"); }
|
|
|
|
bool ImageWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick && ev.target->id() == TBIDC("remove")) {
|
|
Element* image = ev.target->parent();
|
|
image->parent()->RemoveChild(image);
|
|
delete image;
|
|
return true;
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == PageWindow =============================================================
|
|
|
|
PageWindow::PageWindow() {
|
|
LoadResourceFile("test_scroller_snap.tb.txt");
|
|
|
|
// Listen to the pagers scroller
|
|
if (auto pager = GetElementById<Element>(TBIDC("page-scroller")))
|
|
pager->scroller()->SetSnapListener(this);
|
|
}
|
|
|
|
bool PageWindow::OnEvent(const Event& ev) { return DemoWindow::OnEvent(ev); }
|
|
|
|
void PageWindow::OnScrollSnap(Element* target_element, int& target_x,
|
|
int& target_y) {
|
|
int page_w = target_element->padding_rect().w;
|
|
int target_page = (target_x + page_w / 2) / page_w;
|
|
target_x = target_page * page_w;
|
|
}
|
|
|
|
// == AnimationsWindow ========================================================
|
|
|
|
AnimationsWindow::AnimationsWindow() {
|
|
LoadResourceFile("test_animations.tb.txt");
|
|
Animate();
|
|
}
|
|
|
|
void AnimationsWindow::Animate() {
|
|
// Abort any still unfinished animations.
|
|
ElementAnimationManager::AbortAnimations(this);
|
|
|
|
AnimationCurve curve = AnimationCurve::kSlowDown;
|
|
uint64_t duration = 500;
|
|
bool fade = true;
|
|
|
|
if (ListBox* curve_select = GetElementById<ListBox>("curve")) {
|
|
curve = static_cast<AnimationCurve>(curve_select->value());
|
|
}
|
|
if (SpinBox* duration_select = GetElementById<SpinBox>("duration")) {
|
|
duration = uint64_t(duration_select->double_value());
|
|
}
|
|
if (CheckBox* fade_check = GetElementById<CheckBox>("fade")) {
|
|
fade = fade_check->value() ? true : false;
|
|
}
|
|
|
|
// Start move animation
|
|
Animation* anim = new RectElementAnimation(
|
|
this, rect().Offset(-rect().x - rect().w, 0), rect());
|
|
AnimationManager::StartAnimation(anim, curve, duration);
|
|
// Start fade animation
|
|
if (fade) {
|
|
auto anim = new OpacityElementAnimation(
|
|
this, ElementAnimation::kAlmostZeroOpacity, 1, false);
|
|
AnimationManager::StartAnimation(anim, AnimationCurve::kSlowDown, duration);
|
|
}
|
|
}
|
|
|
|
bool AnimationsWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick && ev.target->id() == TBIDC("Animate!"))
|
|
Animate();
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
// == MainWindow ==============================================================
|
|
|
|
MainWindow::MainWindow() {
|
|
LoadResourceFile("test_ui.tb.txt");
|
|
|
|
set_opacity(0.97f);
|
|
}
|
|
|
|
void MainWindow::OnMessageReceived(Message* msg) {
|
|
if (msg->message_id() == TBIDC("instantmsg")) {
|
|
MessageWindow* msg_win = new MessageWindow(this, TBIDC("test_dialog"));
|
|
msg_win->Show("Message window", "Instant message received!");
|
|
} else if (msg->message_id() == TBIDC("busy")) {
|
|
// Keep the message queue busy by posting another "busy" message.
|
|
PostMessage(TBIDC("busy"), nullptr);
|
|
} else if (msg->message_id() == TBIDC("delayedmsg")) {
|
|
MessageWindow* msg_win = new MessageWindow(this, TBIDC(""));
|
|
msg_win->Show("Message window",
|
|
el::util::format_string(
|
|
"Delayed message received!\n\n"
|
|
"It was received %d ms after its intended fire time.",
|
|
int(util::GetTimeMS() - msg->fire_time_millis())));
|
|
}
|
|
}
|
|
|
|
bool MainWindow::OnEvent(const Event& ev) {
|
|
if (ev.type == EventType::kClick) {
|
|
if (ev.target->id() == TBIDC("new")) {
|
|
new MainWindow();
|
|
return true;
|
|
}
|
|
if (ev.target->id() == TBIDC("msg")) {
|
|
PostMessage(TBIDC("instantmsg"), nullptr);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("busymsg")) {
|
|
if (ev.target->value() == 1) {
|
|
// Post the first "busy" message when we check the checkbox.
|
|
assert(!GetMessageById(TBIDC("busy")));
|
|
if (!GetMessageById(TBIDC("busy"))) {
|
|
PostMessage(TBIDC("busy"), nullptr);
|
|
MessageWindow* msg_win =
|
|
new MessageWindow(this, TBIDC("test_dialog"));
|
|
msg_win->Show("Message window",
|
|
"The message loop is now constantly busy with messages "
|
|
"to process.\n\n"
|
|
"The main thread should be working hard, but input & "
|
|
"animations should still be running smoothly.");
|
|
}
|
|
} else {
|
|
// Remove any pending "busy" message when we uncheck the checkbox.
|
|
assert(GetMessageById(TBIDC("busy")));
|
|
if (Message* busymsg = GetMessageById(TBIDC("busy")))
|
|
DeleteMessage(busymsg);
|
|
}
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("delayedmsg")) {
|
|
PostMessageDelayed(TBIDC("delayedmsg"), nullptr, 2000);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("Window.close")) {
|
|
// Intercept the Window.close message and stop it from bubbling
|
|
// to Window (prevent the window from closing)
|
|
MessageWindow* msg_win =
|
|
new MessageWindow(this, TBIDC("confirm_close_dialog"));
|
|
MessageWindowSettings settings(MessageWindowButtons::kYesNo,
|
|
TBIDC("Icon48"));
|
|
settings.dimmer = true;
|
|
settings.styling = true;
|
|
msg_win->Show("Are you sure?",
|
|
"Really <color #0794f8>close</color> the window?",
|
|
&settings);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("confirm_close_dialog")) {
|
|
if (ev.ref_id == TBIDC("MessageWindow.yes")) Close();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("reload skin bitmaps")) {
|
|
int reload_count = 10;
|
|
uint64_t t1 = util::GetTimeMS();
|
|
for (int i = 0; i < reload_count; ++i) {
|
|
Skin::get()->ReloadBitmaps();
|
|
}
|
|
uint64_t t2 = util::GetTimeMS();
|
|
|
|
MessageWindow* msg_win = new MessageWindow(ev.target, TBID());
|
|
msg_win->Show("GFX load performance",
|
|
el::util::format_string(
|
|
"Reloading the skin graphics %d times took %dms",
|
|
reload_count, (int)(t2 - t1)));
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test context lost")) {
|
|
Renderer::get()->InvokeContextLost();
|
|
Renderer::get()->InvokeContextRestored();
|
|
MessageWindow* msg_win = new MessageWindow(ev.target, TBID());
|
|
msg_win->Show("Context lost & restore",
|
|
"Called InvokeContextLost and InvokeContextRestored.\n\n"
|
|
"Does everything look fine?");
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-layout")) {
|
|
std::string resource_file("");
|
|
resource_file.append(ev.target->data.as_string());
|
|
new LayoutWindow(resource_file);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-connections")) {
|
|
new ConnectionWindow();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-list")) {
|
|
new AdvancedListWindow(&advanced_source);
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-image")) {
|
|
new ImageWindow();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-page")) {
|
|
new PageWindow();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-animations")) {
|
|
new AnimationsWindow();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-scroll-container")) {
|
|
new ScrollContainerWindow();
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-skin-conditions")) {
|
|
(new DemoWindow())->LoadResourceFile("test_skin_conditions01.tb.txt");
|
|
(new DemoWindow())->LoadResourceFile("test_skin_conditions02.tb.txt");
|
|
return true;
|
|
} else if (ev.target->id() == TBIDC("test-resource-edit")) {
|
|
ResourceEditWindow* res_edit_win = new ResourceEditWindow();
|
|
res_edit_win->Load("resource_edit_test.tb.txt");
|
|
parent()->AddChild(res_edit_win);
|
|
return true;
|
|
} else if (ev.type == EventType::kClick &&
|
|
ev.target->id() == TBIDC("debug settings")) {
|
|
#ifdef EL_RUNTIME_DEBUG_INFO
|
|
util::ShowDebugInfoSettingsWindow(parent_root());
|
|
#else
|
|
MessageWindow* msg_win = new MessageWindow(ev.target, TBID());
|
|
msg_win->Show("Debug settings",
|
|
"Debug settings is only available in builds "
|
|
"compiled with EL_RUNTIME_DEBUG_INFO defined.\n\n"
|
|
"Debug builds enable this by default.");
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
return DemoWindow::OnEvent(ev);
|
|
}
|
|
|
|
} // namespace testbed
|