29Element DefaultOptionTransform(
const EntryState& state) {
30 std::string label = (state.active ?
"> " :
" ") + state.label;
69class MenuBase :
public ComponentBase,
public MenuOption {
71 explicit MenuBase(
const MenuOption& option) : MenuOption(option) {
72 focused_entry() = selected();
75 bool IsHorizontal() {
return ftxui::IsHorizontal(
direction); }
76 void OnChange() { App::PostEventOrExecute(on_change); }
78 void OnEnter() { App::PostEventOrExecute(on_enter); }
81 if (selected() != selected_previous_) {
84 boxes_.resize(size());
85 selected() =
util::clamp(selected(), 0, size() - 1);
86 selected_previous_ =
util::clamp(selected_previous_, 0, size() - 1);
87 selected_focus_ =
util::clamp(selected_focus_, 0, size() - 1);
88 focused_entry() =
util::clamp(focused_entry(), 0, size() - 1);
91 void OnAnimation(animation::Params& params)
override {
92 animator_first_.OnAnimation(params);
93 animator_second_.OnAnimation(params);
94 for (
auto& animator : animator_background_) {
95 animator.OnAnimation(params);
97 for (
auto& animator : animator_foreground_) {
98 animator.OnAnimation(params);
104 UpdateAnimationTarget();
107 const bool is_menu_focused = Focused();
108 if (elements_prefix) {
109 elements.push_back(elements_prefix());
111 elements.reserve(size());
112 for (
int i = 0; i < size(); ++i) {
113 if (i != 0 && elements_infix) {
114 elements.push_back(elements_infix());
116 const bool is_focused = (focused_entry() == i) && is_menu_focused;
117 const bool is_selected = (selected() == i);
119 const EntryState state = {
120 std::string(entries[i]),
false, is_selected, is_focused, i,
123 Element element = (entries_option.transform ? entries_option.transform
124 : DefaultOptionTransform)
126 if (selected_focus_ == i) {
129 element |= AnimatedColorStyle(i);
133 if (elements_postfix) {
134 elements.push_back(elements_postfix());
138 std::reverse(elements.begin(), elements.end());
142 IsHorizontal() ?
hbox(std::move(elements)) : vbox(std::move(elements));
144 if (!underline.enabled) {
145 return bar | reflect(box_);
148 if (IsHorizontal()) {
152 underline.color_active,
153 underline.color_inactive),
159 underline.color_active,
160 underline.color_inactive),
167 void SelectedTakeFocus() {
168 selected_previous_ = selected();
169 selected_focus_ = selected();
229 bool OnEvent(Event event)
override {
231 if (!CaptureMouse(event)) {
235 if (event.is_mouse()) {
236 return OnMouseEvent(event);
240 const int old_selected = selected();
241 if (event == Event::ArrowUp || event == Event::Character(
'k')) {
244 if (event == Event::ArrowDown || event == Event::Character(
'j')) {
247 if (event == Event::ArrowLeft || event == Event::Character(
'h')) {
250 if (event == Event::ArrowRight || event == Event::Character(
'l')) {
253 if (event == Event::PageUp) {
254 selected() -= box_.y_max - box_.y_min;
256 if (event == Event::PageDown) {
257 selected() += box_.y_max - box_.y_min;
259 if (event == Event::Home) {
262 if (event == Event::End) {
263 selected() = size() - 1;
265 if (event == Event::Tab && size()) {
266 selected() = (selected() + 1) % size();
268 if (event == Event::TabReverse && size()) {
269 selected() = (selected() + size() - 1) % size();
272 selected() =
util::clamp(selected(), 0, size() - 1);
274 if (selected() != old_selected) {
275 focused_entry() = selected();
282 if (event == Event::Return) {
290 bool OnMouseEvent(Event event) {
291 if (event.mouse().button == Mouse::WheelDown ||
292 event.mouse().button == Mouse::WheelUp) {
293 return OnMouseWheel(event);
296 if (event.mouse().button != Mouse::None &&
297 event.mouse().button != Mouse::Left) {
300 if (!CaptureMouse(event)) {
303 for (
int i = 0; i < size(); ++i) {
304 if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
311 if (event.mouse().button == Mouse::Left &&
312 event.mouse().motion == Mouse::Pressed) {
313 if (selected() != i) {
315 selected_previous_ = selected();
324 bool OnMouseWheel(Event event) {
325 if (!box_.Contain(event.mouse().x, event.mouse().y)) {
328 const int old_selected = selected();
330 if (event.mouse().button == Mouse::WheelUp) {
333 if (event.mouse().button == Mouse::WheelDown) {
337 selected() =
util::clamp(selected(), 0, size() - 1);
339 if (selected() != old_selected) {
346 void UpdateAnimationTarget() {
348 UpdateUnderlineTarget();
351 void UpdateColorTarget() {
352 if (size() !=
int(animation_background_.size())) {
353 animation_background_.resize(size());
354 animation_foreground_.resize(size());
355 animator_background_.clear();
356 animator_foreground_.clear();
358 const int len = size();
359 animator_background_.reserve(len);
360 animator_foreground_.reserve(len);
361 for (
int i = 0; i < len; ++i) {
362 animation_background_[i] = 0.F;
363 animation_foreground_[i] = 0.F;
364 animator_background_.emplace_back(&animation_background_[i], 0.F,
365 std::chrono::milliseconds(0),
367 animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
368 std::chrono::milliseconds(0),
373 const bool is_menu_focused = Focused();
374 for (
int i = 0; i < size(); ++i) {
375 const bool is_focused = (focused_entry() == i) && is_menu_focused;
376 const bool is_selected = (selected() == i);
377 float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F;
378 if (animator_background_[i].to() != target) {
379 animator_background_[i] = animation::Animator(
380 &animation_background_[i], target,
381 entries_option.animated_colors.background.duration,
382 entries_option.animated_colors.background.function);
383 animator_foreground_[i] = animation::Animator(
384 &animation_foreground_[i], target,
385 entries_option.animated_colors.foreground.duration,
386 entries_option.animated_colors.foreground.function);
393 if (entries_option.animated_colors.foreground.enabled) {
394 style = style |
color(Color::Interpolate(
395 animation_foreground_[i],
396 entries_option.animated_colors.foreground.inactive,
397 entries_option.animated_colors.foreground.active));
400 if (entries_option.animated_colors.background.enabled) {
401 style = style |
bgcolor(Color::Interpolate(
402 animation_background_[i],
403 entries_option.animated_colors.background.inactive,
404 entries_option.animated_colors.background.active));
409 void UpdateUnderlineTarget() {
410 if (!underline.enabled) {
414 if (FirstTarget() == animator_first_.to() &&
415 SecondTarget() == animator_second_.to()) {
419 if (FirstTarget() >= animator_first_.to()) {
420 animator_first_ = animation::Animator(
421 &first_, FirstTarget(), underline.follower_duration,
422 underline.follower_function, underline.follower_delay);
424 animator_second_ = animation::Animator(
425 &second_, SecondTarget(), underline.leader_duration,
426 underline.leader_function, underline.leader_delay);
428 animator_first_ = animation::Animator(
429 &first_, FirstTarget(), underline.leader_duration,
430 underline.leader_function, underline.leader_delay);
432 animator_second_ = animation::Animator(
433 &second_, SecondTarget(), underline.follower_duration,
434 underline.follower_function, underline.follower_delay);
438 bool Focusable() const final {
return entries.size(); }
439 int size()
const {
return int(entries.size()); }
440 float FirstTarget() {
441 if (boxes_.empty()) {
444 const int value = IsHorizontal() ? boxes_[selected()].x_min - box_.x_min
445 : boxes_[selected()].y_min - box_.y_min;
448 float SecondTarget() {
449 if (boxes_.empty()) {
452 const int value = IsHorizontal() ? boxes_[selected()].x_max - box_.x_min
453 : boxes_[selected()].y_max - box_.y_min;
458 int selected_previous_ = selected();
459 int selected_focus_ = selected();
462 std::vector<Box> boxes_;
468 animation::Animator animator_first_ = animation::Animator(&first_, 0.F);
469 animation::Animator animator_second_ = animation::Animator(&second_, 0.F);
470 std::vector<animation::Animator> animator_background_;
471 std::vector<animation::Animator> animator_foreground_;
472 std::vector<float> animation_background_;
473 std::vector<float> animation_foreground_;
536Component Menu(ConstStringListRef entries,
int* selected, MenuOption option) {
537 option.entries = std::move(entries);
538 option.selected = selected;
548 return Menu(std::move(entries), selected, MenuOption::Toggle());
578 option.label = std::move(label);
608 class Impl :
public ComponentBase,
public MenuEntryOption {
610 explicit Impl(MenuEntryOption option)
611 : MenuEntryOption(std::move(option)) {}
615 const bool is_focused = Focused();
616 UpdateAnimationTarget();
618 const EntryState state{
619 std::string(label()),
false, hovered_, is_focused, Index(),
622 Element element = (transform ? transform : DefaultOptionTransform)
629 return element | AnimatedColorStyle() | reflect(box_);
632 void UpdateAnimationTarget() {
633 const bool focused = Focused();
634 float target = focused ? 1.F : hovered_ ? 0.5F : 0.F;
635 if (target == animator_background_.to()) {
638 animator_background_ = animation::Animator(
639 &animation_background_, target, animated_colors.background.duration,
640 animated_colors.background.function);
641 animator_foreground_ = animation::Animator(
642 &animation_foreground_, target, animated_colors.foreground.duration,
643 animated_colors.foreground.function);
648 if (animated_colors.foreground.enabled) {
650 color(Color::Interpolate(animation_foreground_,
651 animated_colors.foreground.inactive,
652 animated_colors.foreground.active));
655 if (animated_colors.background.enabled) {
657 bgcolor(Color::Interpolate(animation_background_,
658 animated_colors.background.inactive,
659 animated_colors.background.active));
664 bool Focusable()
const override {
return true; }
665 bool OnEvent(Event event)
override {
666 if (!event.is_mouse()) {
670 hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
676 if (event.mouse().button == Mouse::Left &&
677 event.mouse().motion == Mouse::Pressed) {
685 void OnAnimation(animation::Params& params)
override {
686 animator_background_.OnAnimation(params);
687 animator_foreground_.OnAnimation(params);
691 bool hovered_ =
false;
693 float animation_background_ = 0.F;
694 float animation_foreground_ = 0.F;
695 animation::Animator animator_background_ =
696 animation::Animator(&animation_background_, 0.F);
697 animation::Animator animator_foreground_ =
698 animation::Animator(&animation_foreground_, 0.F);
Component Toggle(ConstStringListRef entries, int *selected)
元素的水平列表。使用者可以在其中導航。
Component Menu(ConstStringListRef entries, int *selected_, MenuOption options=MenuOption::Vertical())
一份文字清單。取得焦點的元素會被選取。
Component MenuEntry(ConstStringRef label, MenuEntryOption options={})
一個特定的選單項目。可以將它們放入 Container::Vertical 以 組成一個選單。
Element text(std::string_view text)
顯示一段 UTF8 編碼的 Unicode 文字。
Element xflex(Element)
在 X 軸上盡可能擴展/在需要時最小化。
Element nothing(Element element)
一個什麼都不做的裝飾器。
Element bold(Element child)
使用粗體字型,用於需要更多強調的元素。
Element yflex(Element)
在 Y 軸上盡可能擴展/在需要時最小化。
Direction
Direction 是一個列舉,表示四個主要方向。
Element inverted(Element child)
添加一個濾鏡,它將反轉前景和背景 顏色。
Element focus(Element child)
將 child 設置為其同級元素中被聚焦的元素。
Element bgcolor(Color color, Element child)
設定元素的背景顏色。
Element color(Color color, Element child)
設定元素的前景顏色。
Element vbox(Elements children)
一個垂直一個接一個顯示元素的容器。
float Linear(float p)
模仿直線 y = x
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Element separatorVSelector(float up, float down, Color unselected_color, Color selected_color)
繪製一個垂直條,其中上方/下方之間的區域以不同顏色顯示。
std::shared_ptr< T > Make(Args &&... args)
std::shared_ptr< Node > Element
FTXUI_EXPORT(DOM) Element separatorCharacter(std Element separatorHSelector(float left, float right, Color unselected_color, Color selected_color)
繪製一個水平條,其中左右兩側之間的區域以不同顏色顯示。
Element hbox(Elements children)
一個逐一水平顯示元素的容器。
std::vector< Element > Elements
std::function< Element(Element)> Decorator
Decorator reflect(Box &box)
std::shared_ptr< ComponentBase > Component