MethaneKit
580 строк · 22.8 Кб
1/******************************************************************************
2
3Copyright 2020 Evgeny Gorodetskiy
4
5Licensed under the Apache License, Version 2.0 (the "License"),
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17*******************************************************************************
18
19FILE: TypographyApp.cpp
20Tutorial demonstrating dynamic text rendering and fonts management with Methane Kit.
21
22******************************************************************************/
23
24#include "TypographyApp.h"
25#include "TypographyAppController.h"
26
27#include <Methane/Tutorials/AppSettings.h>
28#include <Methane/Data/TimeAnimation.h>
29
30#include <magic_enum.hpp>
31#include <array>
32
33namespace Methane::Tutorials
34{
35
36struct FontSettings
37{
38gui::Font::Description desc;
39gfx::Color3F color;
40};
41
42constexpr int32_t g_margin_size_in_dots = 32;
43constexpr int32_t g_top_text_pos_in_dots = 110;
44constexpr size_t g_text_blocks_count = 3;
45
46static const std::array<FontSettings, g_text_blocks_count> g_font_settings { {
47{ { "European", "Fonts/Roboto/Roboto-Regular.ttf", 20U }, { 1.F, 1.F, 0.5F } },
48{ { "Japanese", "Fonts/SawarabiMincho/SawarabiMincho-Regular.ttf", 20U }, { 1.F, 0.3F, 0.1F } },
49{ { "Calligraphic", "Fonts/Playball/Playball-Regular.ttf", 20U }, { 0.5F, 1.F, 0.5F } }
50} };
51
52static const gfx::Color3F g_misc_font_color { 1.F, 1.F, 1.F };
53static const std::map<std::string, gfx::Color3F, std::less<>> g_font_color_by_name {
54{ g_font_settings[0].desc.name, g_font_settings[0].color },
55{ g_font_settings[1].desc.name, g_font_settings[1].color },
56{ g_font_settings[2].desc.name, g_font_settings[2].color },
57};
58
59// Pangrams from http://clagnut.com/blog/2380/
60static const std::array<std::u32string, g_text_blocks_count> g_text_blocks = { {
61// 0: european pangrams
62gui::Font::ConvertUtf8To32(
63"The quick brown fox jumps over the lazy dog!\n" \
64"Съешь ещё этих мягких французских булок, да выпей чаю.\n" \
65"Ο καλύμνιος σφουγγαράς ψιθύρισε πως θα βουτήξει χωρίς να διστάζει.\n" \
66"Pijamalı hasta, yağız şoföre çabucak güvendi."),
67
68// 1: japanese pangram
69gui::Font::ConvertUtf8To32(
70"いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす"),
71
72// 2: hitchhiker's guide quote
73gui::Font::ConvertUtf8To32(
74"A towel is about the most massively useful thing an interstellar hitchhiker can have. " \
75"Partly it has great practical value. You can wrap it around you for warmth as you bound across the cold moons of Jaglan Beta; " \
76"you can lie on it on the brilliant marble-sanded beaches of Santraginus V, inhaling the heady sea vapors; " \
77"you can sleep under it beneath the stars which shine so redly on the desert world of Kakrafoon; " \
78"use it to sail a miniraft down the slow heavy River Moth; " \
79"wet it for use in hand-to-hand-combat; " \
80"wrap it round your head to ward off noxious fumes or avoid the gaze of the Ravenous Bugblatter Beast of Traal " \
81"(such a mind-boggingly stupid animal, it assumes that if you can't see it, it can't see you); " \
82"you can wave your towel in emergencies as a distress signal, and of course dry yourself off with it if it still seems to be clean enough.")
83}};
84
85static const std::map<pin::Keyboard::State, TypographyAppAction> g_typography_action_by_keyboard_state{
86{ { pin::Keyboard::Key::W }, TypographyAppAction::SwitchTextWrapMode },
87{ { pin::Keyboard::Key::H }, TypographyAppAction::SwitchTextHorizontalAlignment },
88{ { pin::Keyboard::Key::V }, TypographyAppAction::SwitchTextVerticalAlignment },
89{ { pin::Keyboard::Key::U }, TypographyAppAction::SwitchIncrementalTextUpdate },
90{ { pin::Keyboard::Key::D }, TypographyAppAction::SwitchTypingDirection },
91{ { pin::Keyboard::Key::Equal }, TypographyAppAction::SpeedupTyping },
92{ { pin::Keyboard::Key::Minus }, TypographyAppAction::SlowdownTyping },
93};
94
95[[nodiscard]]
96static gui::UnitRect GetTextBlockRectInDots(size_t block_index, int32_t vertical_pos_in_dots, const gfx::FrameSize& frame_size_in_dots)
97{
98return gui::UnitRect(
99gui::Units::Dots,
100gfx::Point2I
101{
102g_margin_size_in_dots,
103vertical_pos_in_dots
104},
105gfx::FrameSize
106{
107frame_size_in_dots.GetWidth() - 2 * g_margin_size_in_dots,
108block_index == g_text_blocks_count - 1
109? frame_size_in_dots.GetHeight() - vertical_pos_in_dots - g_margin_size_in_dots // last text block fills all available space
110: 0U // other text blocks have calculated height
111}
112);
113}
114
115inline Timer::TimeDuration UpdateTextRect(gui::TextItem& text, const gui::UnitRect& text_block_rect)
116{
117Methane::ScopeTimer scope_timer("Text update");
118text.SetRect(text_block_rect);
119return scope_timer.GetElapsedDuration();
120}
121
122inline Timer::TimeDuration UpdateText(const gui::Text& text, const std::u32string& displayed_text, const gui::UnitRect& text_block_rect)
123{
124Methane::ScopeTimer scope_timer("Text update");
125text.SetTextInScreenRect(displayed_text, text_block_rect);
126return scope_timer.GetElapsedDuration();
127}
128
129TypographyApp::TypographyApp()
130: UserInterfaceApp(
131GetGraphicsTutorialAppSettings("Methane Typography", AppOptions::GetDefaultWithColorOnlyAndAnim()),
132{ gui::HeadsUpDisplayMode::UserInterface },
133"Dynamic text rendering and fonts management tutorial.")
134, m_font_context(UserInterfaceApp::GetFontContext().GetFontLibrary(), Data::FontProvider::Get())
135{
136m_displayed_text_lengths.resize(g_text_blocks_count, 0);
137m_displayed_text_lengths[0] = 1;
138
139GetHeadsUpDisplaySettings().position = gui::UnitPoint(gui::Units::Dots, g_margin_size_in_dots, g_margin_size_in_dots);
140
141m_font_context.GetFontLibrary().Connect(*this);
142AddInputControllers({
143std::make_shared<TypographyAppController>(*this, g_typography_action_by_keyboard_state)
144});
145
146// Setup animations
147GetAnimations().push_back(
148std::make_shared<Data::TimeAnimation>(std::bind(&TypographyApp::Animate, this, std::placeholders::_1, std::placeholders::_2))
149);
150
151ShowParameters();
152}
153
154TypographyApp::~TypographyApp()
155{
156// Wait for GPU rendering is completed to release resources
157WaitForRenderComplete();
158
159// Clear the font library to release all atlas textures
160m_font_context.GetFontLibrary().Clear();
161m_font_atlas_badges.clear();
162}
163
164void TypographyApp::Init()
165{
166UserInterfaceApp::Init();
167
168const gfx::FrameSize frame_size_in_dots = GetFrameSizeInDots();
169const uint32_t frame_width_without_margins = frame_size_in_dots.GetWidth() - 2 * g_margin_size_in_dots;
170int32_t vertical_text_pos_in_dots = g_top_text_pos_in_dots;
171
172for(size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
173{
174const FontSettings& font_settings = g_font_settings[block_index];
175const size_t displayed_text_length = m_displayed_text_lengths[block_index];
176const std::u32string displayed_text_block = g_text_blocks[block_index].substr(0, displayed_text_length);
177
178// Add font to library
179m_fonts.push_back(
180m_font_context.GetFont(
181gui::Font::Settings
182{
183font_settings.desc,
184GetUIContext().GetFontResolutionDpi(),
185gui::Font::GetAlphabetFromText(displayed_text_block)
186}
187)
188);
189
190// Add text element
191m_texts.push_back(
192std::make_shared<gui::TextItem>(GetUIContext(), m_fonts.back(),
193gui::Text::SettingsUtf32
194{
195font_settings.desc.name,
196displayed_text_block,
197gui::UnitRect
198{
199gui::Units::Dots,
200gfx::Point2I { g_margin_size_in_dots, vertical_text_pos_in_dots },
201gfx::FrameSize { frame_width_without_margins, 0U /* calculated height */ }
202},
203m_settings.text_layout,
204gfx::Color4F(font_settings.color, 1.F),
205m_settings.is_incremental_text_update
206}
207)
208);
209
210vertical_text_pos_in_dots = m_texts.back()->GetRectInDots().GetBottom() + g_margin_size_in_dots;
211}
212
213UpdateFontAtlasBadges();
214
215// Create per-frame command lists
216for(TypographyFrame& frame : GetFrames())
217{
218frame.render_cmd_list = GetRenderContext().GetRenderCommandKit().GetQueue().CreateRenderCommandList(frame.screen_pass);
219frame.render_cmd_list.SetName(fmt::format("Text Rendering {}", frame.index));
220frame.execute_cmd_list_set = rhi::CommandListSet({ frame.render_cmd_list.GetInterface() }, frame.index);
221}
222
223CompleteInitialization();
224}
225
226Ptr<gui::Badge> TypographyApp::CreateFontAtlasBadge(const gui::Font& font, const rhi::Texture& atlas_texture)
227{
228const auto font_color_by_name_it = g_font_color_by_name.find(font.GetSettings().description.name);
229const gui::Color3F& font_color = font_color_by_name_it != g_font_color_by_name.end()
230? font_color_by_name_it->second : g_misc_font_color;
231
232return std::make_shared<gui::Badge>(
233GetUIContext(), atlas_texture,
234gui::Badge::Settings
235{
236font.GetSettings().description.name + " Font Atlas",
237gui::Badge::FrameCorner::BottomLeft,
238gui::UnitSize(gui::Units::Pixels, static_cast<const gfx::FrameSize&>(atlas_texture.GetSettings().dimensions)),
239gui::UnitSize(gui::Units::Dots, 16U, 16U),
240gui::Color4F(font_color, 0.5F),
241gui::Badge::TextureMode::RFloatToAlpha,
242}
243);
244}
245
246void TypographyApp::UpdateFontAtlasBadges()
247{
248const std::vector<gui::Font> fonts = m_font_context.GetFontLibrary().GetFonts();
249const rhi::RenderContext& context = GetRenderContext();
250
251// Remove obsolete font atlas badges
252for(auto badge_it = m_font_atlas_badges.begin(); badge_it != m_font_atlas_badges.end();)
253{
254META_CHECK_ARG_NOT_NULL(*badge_it);
255if (const gui::Badge& badge = **badge_it;
256std::any_of(fonts.begin(), fonts.end(),
257[&badge, &context](const gui::Font& font)
258{ return badge.GetTexture() == font.GetAtlasTexture(context); }))
259{
260++badge_it;
261continue;
262}
263
264badge_it = m_font_atlas_badges.erase(badge_it);
265}
266
267// Add new font atlas badges
268for(const gui::Font& font : fonts)
269{
270const rhi::Texture& font_atlas_texture = font.GetAtlasTexture(context);
271if (!font_atlas_texture.IsInitialized() ||
272std::any_of(m_font_atlas_badges.begin(), m_font_atlas_badges.end(),
273[&font_atlas_texture](const Ptr<gui::Badge>& font_atlas_badge_ptr)
274{
275return font_atlas_badge_ptr->GetTexture() == font_atlas_texture;
276}))
277continue;
278
279m_font_atlas_badges.emplace_back(CreateFontAtlasBadge(font, font_atlas_texture));
280}
281
282LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
283}
284
285void TypographyApp::LayoutFontAtlasBadges(const gfx::FrameSize& frame_size)
286{
287// Sort atlas badges by size so that largest are displayed first
288std::sort(m_font_atlas_badges.begin(), m_font_atlas_badges.end(),
289[](const Ptr<gui::Badge>& left_ptr, const Ptr<gui::Badge>& right_ptr)
290{
291return left_ptr->GetQuadSettings().screen_rect.size.GetPixelsCount() >
292right_ptr->GetQuadSettings().screen_rect.size.GetPixelsCount();
293}
294);
295
296// Layout badges in a row one after another with a margin spacing
297gui::UnitSize badge_margins(gui::Units::Dots, g_margin_size_in_dots, g_margin_size_in_dots);
298for(const Ptr<gui::Badge>& badge_atlas_ptr : m_font_atlas_badges)
299{
300META_CHECK_ARG_NOT_NULL(badge_atlas_ptr);
301const gui::UnitSize atlas_size = GetUIContext().ConvertTo<gui::Units::Dots>(badge_atlas_ptr->GetTexture().GetSettings().dimensions.AsRectSize());
302badge_atlas_ptr->FrameResize(gui::UnitSize(gui::Units::Pixels, frame_size), atlas_size, badge_margins);
303badge_margins += gui::UnitSize(gui::Units::Dots, atlas_size.GetWidth() + g_margin_size_in_dots, 0U);
304}
305}
306
307bool TypographyApp::Resize(const gfx::FrameSize& frame_size, bool is_minimized)
308{
309// Resize screen color and depth textures
310if (!UserInterfaceApp::Resize(frame_size, is_minimized))
311return false;
312
313const gfx::FrameSize frame_size_in_dots = GetFrameSizeInDots();
314int32_t vertical_text_pos_in_dots = g_top_text_pos_in_dots;
315
316for (size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
317{
318const Ptr<gui::TextItem>& text_ptr = m_texts[block_index];
319const gui::UnitRect text_block_rect = GetTextBlockRectInDots(block_index, vertical_text_pos_in_dots, frame_size_in_dots);
320m_text_update_duration = UpdateTextRect(*text_ptr, text_block_rect);
321vertical_text_pos_in_dots += text_ptr->GetRectInDots().size.GetHeight() + g_margin_size_in_dots;
322}
323
324LayoutFontAtlasBadges(frame_size);
325UpdateParametersText(); // show text update timing
326return true;
327}
328
329bool TypographyApp::Animate(double elapsed_seconds, double)
330{
331if (elapsed_seconds - m_text_update_elapsed_sec < m_settings.typing_update_interval_sec)
332return true;
333
334m_text_update_elapsed_sec = elapsed_seconds;
335
336int32_t vertical_text_pos_in_dots = g_top_text_pos_in_dots;
337for(size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
338{
339AnimateTextBlock(block_index, vertical_text_pos_in_dots);
340}
341
342UpdateParametersText();
343return true;
344}
345
346void TypographyApp::AnimateTextBlock(size_t block_index, int32_t& vertical_text_pos_in_dots)
347{
348const gui::TextItem& text = *m_texts[block_index];
349const std::u32string& full_text = g_text_blocks[block_index];
350const size_t text_block_length = full_text.length();
351size_t& displayed_text_length = m_displayed_text_lengths[block_index];
352
353if (displayed_text_length == (m_settings.is_forward_typing_direction ? 0 : text_block_length))
354{
355text.SetText(m_settings.is_forward_typing_direction ? std::u32string() : full_text);
356if (!m_settings.is_forward_typing_direction)
357vertical_text_pos_in_dots = text.GetRectInDots().GetBottom() + g_margin_size_in_dots;
358return;
359}
360
361if (displayed_text_length == (m_settings.is_forward_typing_direction ? text_block_length : 0))
362{
363if (block_index == (m_settings.is_forward_typing_direction ? g_text_blocks_count - 1 : 0))
364{
365ResetAnimation();
366return;
367}
368
369vertical_text_pos_in_dots = text.GetRectInDots().GetBottom() + g_margin_size_in_dots;
370size_t next_block_index = block_index + (m_settings.is_forward_typing_direction ? 1 : -1);
371size_t& next_displayed_text_length = m_displayed_text_lengths[next_block_index];
372
373if (m_settings.is_forward_typing_direction && next_displayed_text_length == 0)
374next_displayed_text_length = 1;
375
376if (!m_settings.is_forward_typing_direction && next_displayed_text_length == g_text_blocks[next_block_index].length())
377next_displayed_text_length = g_text_blocks[next_block_index].length() - 1;
378
379return;
380}
381
382if (m_settings.is_forward_typing_direction)
383displayed_text_length++;
384else
385displayed_text_length--;
386
387const std::u32string displayed_text = full_text.substr(0, displayed_text_length);
388const gui::UnitRect text_block_rect = GetTextBlockRectInDots(block_index, vertical_text_pos_in_dots, GetFrameSizeInDots());
389
390m_text_update_duration = UpdateText(text, displayed_text, text_block_rect);
391
392vertical_text_pos_in_dots = text.GetRectInDots().GetBottom() + g_margin_size_in_dots;
393}
394
395void TypographyApp::ResetAnimation()
396{
397for(size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
398{
399const std::u32string& full_text = g_text_blocks[block_index];
400
401size_t displayed_text_length = block_index ? 0 : 1;
402if (!m_settings.is_forward_typing_direction)
403{
404displayed_text_length = full_text.length();
405if (block_index == g_text_blocks_count - 1)
406displayed_text_length--;
407}
408
409const std::u32string displayed_text = full_text.substr(0, displayed_text_length);
410m_displayed_text_lengths[block_index] = displayed_text_length;
411m_texts[block_index]->SetText(displayed_text);
412m_fonts[block_index].ResetChars(displayed_text);
413}
414
415LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
416}
417
418bool TypographyApp::Update()
419{
420if (!UserInterfaceApp::Update())
421return false;
422
423// Update text block resources
424for(const Ptr<gui::TextItem>& text_ptr : m_texts)
425{
426text_ptr->Update(GetFrameSize());
427}
428
429return true;
430}
431
432bool TypographyApp::Render()
433{
434if (!UserInterfaceApp::Render())
435return false;
436
437const TypographyFrame& frame = GetCurrentFrame();
438
439// Draw text blocks
440META_DEBUG_GROUP_VAR(s_text_debug_group, "Text Blocks Rendering");
441for(const Ptr<gui::TextItem>& text_ptr : m_texts)
442{
443text_ptr->Draw(frame.render_cmd_list, &s_text_debug_group);
444}
445
446// Draw font atlas badges
447META_DEBUG_GROUP_VAR(s_atlas_debug_group, "Font Atlases Rendering");
448for(const Ptr<gui::Badge>& badge_atlas_ptr : m_font_atlas_badges)
449{
450badge_atlas_ptr->Draw(frame.render_cmd_list, &s_atlas_debug_group);
451}
452
453RenderOverlay(frame.render_cmd_list);
454frame.render_cmd_list.Commit();
455
456// Execute command list on render queue and present frame to screen
457GetRenderContext().GetRenderCommandKit().GetQueue().Execute(frame.execute_cmd_list_set);
458GetRenderContext().Present();
459
460return true;
461}
462
463std::string TypographyApp::GetParametersString()
464{
465std::stringstream ss;
466ss << "Typography parameters:"
467<< std::endl << " - text wrap mode: " << magic_enum::enum_name(m_settings.text_layout.wrap)
468<< std::endl << " - horizontal text alignment: " << magic_enum::enum_name(m_settings.text_layout.horizontal_alignment)
469<< std::endl << " - vertical text alignment: " << magic_enum::enum_name(m_settings.text_layout.vertical_alignment)
470<< std::endl << " - text typing mode: " << (m_settings.is_forward_typing_direction ? "Appending" : "Backspace")
471<< std::endl << " - text typing interval (ms): " << static_cast<uint32_t>(m_settings.typing_update_interval_sec * 1000)
472<< std::endl << " - text typing animation: " << (!GetAnimations().IsPaused() ? "ON" : "OFF")
473<< std::endl << " - incremental text updates: " << (m_settings.is_incremental_text_update ? "ON" : "OFF")
474<< std::endl << " - text update duration (us): " << static_cast<double>(m_text_update_duration.count()) / 1000;
475
476return ss.str();
477}
478
479void TypographyApp::SetTextLayout(const gui::Text::Layout& text_layout)
480{
481if (m_settings.text_layout == text_layout)
482return;
483
484m_settings.text_layout = text_layout;
485for (const Ptr<gui::TextItem>& text_ptr : m_texts)
486{
487text_ptr->SetLayout(text_layout);
488}
489
490UpdateParametersText();
491}
492
493void TypographyApp::SetForwardTypingDirection(bool is_forward_typing_direction)
494{
495if (m_settings.is_forward_typing_direction == is_forward_typing_direction)
496return;
497
498m_settings.is_forward_typing_direction = is_forward_typing_direction;
499UpdateParametersText();
500}
501
502void TypographyApp::SetTextUpdateInterval(double text_update_interval_sec)
503{
504if (std::abs(m_settings.typing_update_interval_sec - text_update_interval_sec) < 0.000001)
505return;
506
507m_settings.typing_update_interval_sec = text_update_interval_sec;
508UpdateParametersText();
509}
510
511void TypographyApp::SetIncrementalTextUpdate(bool is_incremental_text_update)
512{
513if (m_settings.is_incremental_text_update == is_incremental_text_update)
514return;
515
516m_settings.is_incremental_text_update = is_incremental_text_update;
517for(const Ptr<gui::TextItem>& text_ptr : m_texts)
518{
519text_ptr->SetIncrementalUpdate(is_incremental_text_update);
520}
521
522UpdateParametersText();
523}
524
525void TypographyApp::OnContextReleased(rhi::IContext& context)
526{
527m_font_context.GetFontLibrary().Clear();
528
529m_fonts.clear();
530m_texts.clear();
531m_font_atlas_badges.clear();
532
533UserInterfaceApp::OnContextReleased(context);
534}
535
536void TypographyApp::OnFontAdded(gui::Font& font)
537{
538font.Connect(*this);
539}
540
541void TypographyApp::OnFontAtlasTextureReset(gui::Font& font, const rhi::Texture* old_atlas_texture_ptr, const rhi::Texture* new_atlas_texture_ptr)
542{
543const auto font_atlas_badge_ptr_it = old_atlas_texture_ptr
544? std::find_if(m_font_atlas_badges.begin(), m_font_atlas_badges.end(),
545[&old_atlas_texture_ptr](const Ptr<gui::Badge>& font_atlas_badge_ptr)
546{ return font_atlas_badge_ptr->GetTexture() == *old_atlas_texture_ptr; })
547: m_font_atlas_badges.end();
548
549if (new_atlas_texture_ptr)
550{
551if (font_atlas_badge_ptr_it == m_font_atlas_badges.end())
552{
553m_font_atlas_badges.emplace_back(CreateFontAtlasBadge(font, *new_atlas_texture_ptr));
554LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
555}
556else
557{
558const Ptr<gui::Badge>& badge_ptr = *font_atlas_badge_ptr_it;
559badge_ptr->SetTexture(*new_atlas_texture_ptr);
560badge_ptr->SetSize(gui::UnitSize(gui::Units::Pixels, static_cast<const gfx::FrameSize&>(new_atlas_texture_ptr->GetSettings().dimensions)));
561}
562}
563else if (font_atlas_badge_ptr_it != m_font_atlas_badges.end())
564{
565m_font_atlas_badges.erase(font_atlas_badge_ptr_it);
566LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
567}
568}
569
570void TypographyApp::OnFontAtlasUpdated(gui::Font&)
571{
572LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
573}
574
575} // namespace Methane::Tutorials
576
577int main(int argc, const char* argv[])
578{
579return Methane::Tutorials::TypographyApp().Run({ argc, argv });
580}
581