mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-18 09:08:08 +08:00
Animation (#355)
This commit is contained in:
285
src/ftxui/component/animation.cpp
Normal file
285
src/ftxui/component/animation.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath> // for sin, pow, sqrt, M_PI_2, M_PI, cos
|
||||
|
||||
#include "ftxui/component/animation.hpp"
|
||||
|
||||
#include <ratio> // for ratio
|
||||
|
||||
namespace ftxui {
|
||||
namespace animation {
|
||||
|
||||
namespace easing {
|
||||
// Easing function have been taken out of:
|
||||
// https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c
|
||||
//
|
||||
// Corresponding license:
|
||||
// Copyright (c) 2011, Auerhaus Development, LLC
|
||||
//
|
||||
// This program is free software. It comes without any warranty, to
|
||||
// the extent permitted by applicable law. You can redistribute it
|
||||
// and/or modify it under the terms of the Do What The Fuck You Want
|
||||
// To Public License, Version 2, as published by Sam Hocevar. See
|
||||
// http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
|
||||
// Modeled after the line y = x
|
||||
float Linear(float p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
// Modeled after the parabola y = x^2
|
||||
float QuadraticIn(float p) {
|
||||
return p * p;
|
||||
}
|
||||
|
||||
// Modeled after the parabola y = -x^2 + 2x
|
||||
float QuadraticOut(float p) {
|
||||
return -(p * (p - 2));
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quadratic
|
||||
// y = (1/2)((2x)^2) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
|
||||
float QuadraticInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 2 * p * p;
|
||||
} else {
|
||||
return (-2 * p * p) + (4 * p) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = x^3
|
||||
float CubicIn(float p) {
|
||||
return p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the cubic y = (x - 1)^3 + 1
|
||||
float CubicOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise cubic
|
||||
// y = (1/2)((2x)^3) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
|
||||
float CubicInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 4 * p * p * p;
|
||||
} else {
|
||||
float f = ((2 * p) - 2);
|
||||
return 0.5 * f * f * f + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quartic x^4
|
||||
float QuarticIn(float p) {
|
||||
return p * p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the quartic y = 1 - (x - 1)^4
|
||||
float QuarticOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f * (1 - p) + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quartic
|
||||
// y = (1/2)((2x)^4) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
|
||||
float QuarticInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 8 * p * p * p * p;
|
||||
} else {
|
||||
float f = (p - 1);
|
||||
return -8 * f * f * f * f + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = x^5
|
||||
float QuinticIn(float p) {
|
||||
return p * p * p * p * p;
|
||||
}
|
||||
|
||||
// Modeled after the quintic y = (x - 1)^5 + 1
|
||||
float QuinticOut(float p) {
|
||||
float f = (p - 1);
|
||||
return f * f * f * f * f + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise quintic
|
||||
// y = (1/2)((2x)^5) ; [0, 0.5)
|
||||
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
|
||||
float QuinticInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 16 * p * p * p * p * p;
|
||||
} else {
|
||||
float f = ((2 * p) - 2);
|
||||
return 0.5 * f * f * f * f * f + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave
|
||||
float SineIn(float p) {
|
||||
return sin((p - 1) * M_PI_2) + 1;
|
||||
}
|
||||
|
||||
// Modeled after quarter-cycle of sine wave (different phase)
|
||||
float SineOut(float p) {
|
||||
return sin(p * M_PI_2);
|
||||
}
|
||||
|
||||
// Modeled after half sine wave
|
||||
float SineInOut(float p) {
|
||||
return 0.5 * (1 - cos(p * M_PI));
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant IV of unit circle
|
||||
float CircularIn(float p) {
|
||||
return 1 - sqrt(1 - (p * p));
|
||||
}
|
||||
|
||||
// Modeled after shifted quadrant II of unit circle
|
||||
float CircularOut(float p) {
|
||||
return sqrt((2 - p) * p);
|
||||
}
|
||||
|
||||
// Modeled after the piecewise circular function
|
||||
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
|
||||
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
|
||||
float CircularInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 0.5 * (1 - sqrt(1 - 4 * (p * p)));
|
||||
} else {
|
||||
return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = 2^(10(x - 1))
|
||||
float ExponentialIn(float p) {
|
||||
return (p == 0.0) ? p : pow(2, 10 * (p - 1));
|
||||
}
|
||||
|
||||
// Modeled after the exponential function y = -2^(-10x) + 1
|
||||
float ExponentialOut(float p) {
|
||||
return (p == 1.0) ? p : 1 - pow(2, -10 * p);
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponential
|
||||
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
|
||||
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
|
||||
float ExponentialInOut(float p) {
|
||||
if (p == 0.0 || p == 1.0)
|
||||
return p;
|
||||
|
||||
if (p < 0.5) {
|
||||
return 0.5 * pow(2, (20 * p) - 10);
|
||||
} else {
|
||||
return -0.5 * pow(2, (-20 * p) + 10) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
|
||||
float ElasticIn(float p) {
|
||||
return sin(13 * M_PI_2 * p) * pow(2, 10 * (p - 1));
|
||||
}
|
||||
|
||||
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) +
|
||||
// 1
|
||||
float ElasticOut(float p) {
|
||||
return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1;
|
||||
}
|
||||
|
||||
// Modeled after the piecewise exponentially-damped sine wave:
|
||||
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
|
||||
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
|
||||
float ElasticInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 0.5 * sin(13 * M_PI_2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1));
|
||||
} else {
|
||||
return 0.5 *
|
||||
(sin(-13 * M_PI_2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) +
|
||||
2);
|
||||
}
|
||||
}
|
||||
|
||||
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
|
||||
float BackIn(float p) {
|
||||
return p * p * p - p * sin(p * M_PI);
|
||||
}
|
||||
|
||||
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
|
||||
float BackOut(float p) {
|
||||
float f = (1 - p);
|
||||
return 1 - (f * f * f - f * sin(f * M_PI));
|
||||
}
|
||||
|
||||
// Modeled after the piecewise overshooting cubic function:
|
||||
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
|
||||
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
|
||||
float BackInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
float f = 2 * p;
|
||||
return 0.5 * (f * f * f - f * sin(f * M_PI));
|
||||
} else {
|
||||
float f = (1 - (2 * p - 1));
|
||||
return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
float BounceIn(float p) {
|
||||
return 1 - BounceOut(1 - p);
|
||||
}
|
||||
|
||||
float BounceOut(float p) {
|
||||
if (p < 4 / 11.0) {
|
||||
return (121 * p * p) / 16.0;
|
||||
} else if (p < 8 / 11.0) {
|
||||
return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0;
|
||||
} else if (p < 9 / 10.0) {
|
||||
return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + 16061 / 1805.0;
|
||||
} else {
|
||||
return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0;
|
||||
}
|
||||
}
|
||||
|
||||
float BounceInOut(float p) {
|
||||
if (p < 0.5) {
|
||||
return 0.5 * BounceIn(p * 2);
|
||||
} else {
|
||||
return 0.5 * BounceOut(p * 2 - 1) + 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace easing
|
||||
|
||||
Animator::Animator(float* from,
|
||||
float to,
|
||||
Duration duration,
|
||||
easing::Function easing_function,
|
||||
Duration delay)
|
||||
: value_(from),
|
||||
from_(*from),
|
||||
to_(to),
|
||||
duration_(duration),
|
||||
easing_function_(easing_function),
|
||||
current_(-delay) {
|
||||
RequestAnimationFrame();
|
||||
}
|
||||
|
||||
void Animator::OnAnimation(Params& params) {
|
||||
current_ += params.duration();
|
||||
|
||||
if (current_ >= duration_) {
|
||||
*value_ = to_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_ <= Duration()) {
|
||||
*value_ = from_;
|
||||
} else {
|
||||
*value_ = from_ + (to_ - from_) * easing_function_(current_ / duration_);
|
||||
}
|
||||
|
||||
RequestAnimationFrame();
|
||||
}
|
||||
|
||||
} // namespace animation
|
||||
} // namespace ftxui
|
@@ -1,66 +1,33 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Button
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return
|
||||
#include "ftxui/component/animation.hpp" // for Animator, Params (ptr only)
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Button
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ButtonOption, AnimatedColorOption, AnimatedColorsOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, nothing, reflect, text, border, inverted
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/util/ref.hpp" // for ConstStringRef, Ref
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Decorator, Element, bgcolor, color, operator|=, reflect, text, border, inverted, nothing
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
class ButtonBase : public ComponentBase {
|
||||
public:
|
||||
ButtonBase(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option)
|
||||
: label_(label), on_click_(on_click), option_(std::move(option)) {}
|
||||
|
||||
// Component implementation:
|
||||
Element Render() override {
|
||||
auto style = Focused() ? inverted : nothing;
|
||||
auto my_border = option_->border ? border : nothing;
|
||||
return text(*label_) | my_border | style | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
|
||||
if (!CaptureMouse(event))
|
||||
return false;
|
||||
|
||||
TakeFocus();
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
on_click_();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
on_click_();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Focusable() const final { return true; }
|
||||
|
||||
private:
|
||||
ConstStringRef label_;
|
||||
std::function<void()> on_click_;
|
||||
Box box_;
|
||||
Ref<ButtonOption> option_;
|
||||
};
|
||||
Element DefaultTransform(EntryState params) {
|
||||
auto element = text(params.label) | border;
|
||||
if (params.active)
|
||||
element |= bold;
|
||||
if (params.focused)
|
||||
element |= inverted;
|
||||
return element;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -90,7 +57,122 @@ class ButtonBase : public ComponentBase {
|
||||
Component Button(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option) {
|
||||
return Make<ButtonBase>(label, std::move(on_click), std::move(option));
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(ConstStringRef label,
|
||||
std::function<void()> on_click,
|
||||
Ref<ButtonOption> option)
|
||||
: label_(label), on_click_(on_click), option_(std::move(option)) {}
|
||||
|
||||
// Component implementation:
|
||||
Element Render() override {
|
||||
float target = Focused() ? 1.0 : 0.f;
|
||||
if (target != animator_background_.to())
|
||||
SetAnimationTarget(target);
|
||||
|
||||
EntryState state = {
|
||||
*label_,
|
||||
false,
|
||||
Active(),
|
||||
Focused(),
|
||||
};
|
||||
|
||||
auto element =
|
||||
(option_->transform ? option_->transform : DefaultTransform) //
|
||||
(state);
|
||||
return element | AnimatedColorStyle() | reflect(box_);
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle() {
|
||||
Decorator style = nothing;
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_foreground_, //
|
||||
option_->animated_colors.background.inactive,
|
||||
option_->animated_colors.background.active));
|
||||
}
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_, //
|
||||
option_->animated_colors.foreground.inactive,
|
||||
option_->animated_colors.foreground.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void SetAnimationTarget(float target) {
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, target,
|
||||
option_->animated_colors.foreground.duration,
|
||||
option_->animated_colors.foreground.function);
|
||||
}
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
animator_background_ =
|
||||
animation::Animator(&animation_background_, target,
|
||||
option_->animated_colors.background.duration,
|
||||
option_->animated_colors.background.function);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& p) override {
|
||||
animator_background_.OnAnimation(p);
|
||||
animator_foreground_.OnAnimation(p);
|
||||
}
|
||||
|
||||
void OnClick() {
|
||||
on_click_();
|
||||
animation_background_ = 0.5f;
|
||||
animation_foreground_ = 0.5f;
|
||||
SetAnimationTarget(1.f);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse())
|
||||
return OnMouseEvent(event);
|
||||
|
||||
if (event == Event::Return) {
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
mouse_hover_ =
|
||||
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
|
||||
|
||||
if (!mouse_hover_)
|
||||
return false;
|
||||
|
||||
TakeFocus();
|
||||
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Focusable() const final { return true; }
|
||||
|
||||
private:
|
||||
ConstStringRef label_;
|
||||
std::function<void()> on_click_;
|
||||
bool mouse_hover_ = false;
|
||||
Box box_;
|
||||
Ref<ButtonOption> option_;
|
||||
float animation_background_ = 0;
|
||||
float animation_foreground_ = 0;
|
||||
animation::Animator animator_background_ =
|
||||
animation::Animator(&animation_background_);
|
||||
animation::Animator animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_);
|
||||
};
|
||||
|
||||
return Make<Impl>(label, std::move(on_click), std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
@@ -18,32 +18,24 @@ namespace {
|
||||
class CheckboxBase : public ComponentBase {
|
||||
public:
|
||||
CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> option)
|
||||
: label_(label), state_(state), option_(std::move(option)) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
if (option_->style_checked == "▣ ")
|
||||
option_->style_checked = "[X]";
|
||||
if (option_->style_unchecked == "☐ ")
|
||||
option_->style_unchecked = "[ ]";
|
||||
#endif
|
||||
}
|
||||
: label_(label), state_(state), option_(std::move(option)) {}
|
||||
|
||||
private:
|
||||
// Component implementation.
|
||||
Element Render() override {
|
||||
bool is_focused = Focused();
|
||||
bool is_active = Active();
|
||||
auto style = (is_focused || hovered_) ? option_->style_selected_focused
|
||||
: is_active ? option_->style_selected
|
||||
: option_->style_normal;
|
||||
auto focus_management = is_focused ? focus : is_active ? select : nothing;
|
||||
return hbox({
|
||||
text(*state_ ? option_->style_checked
|
||||
: option_->style_unchecked),
|
||||
text(*label_) | style | focus_management,
|
||||
}) |
|
||||
reflect(box_);
|
||||
auto state = EntryState{
|
||||
*label_,
|
||||
*state_,
|
||||
is_active,
|
||||
is_focused || hovered_,
|
||||
};
|
||||
auto element = (option_->transform
|
||||
? option_->transform
|
||||
: CheckboxOption::Simple().transform)(std::move(state));
|
||||
return element | focus_management | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
|
@@ -31,8 +31,15 @@ Component Collapsible(ConstStringRef label, Component child, Ref<bool> show) {
|
||||
Impl(ConstStringRef label, Component child, Ref<bool> show)
|
||||
: label_(label), show_(std::move(show)) {
|
||||
CheckboxOption opt;
|
||||
opt.style_checked = "▼ ";
|
||||
opt.style_unchecked = "▶ ";
|
||||
opt.transform = [](EntryState s) {
|
||||
auto prefix = text(s.state ? "▼ " : "▶ ");
|
||||
auto t = text(s.label);
|
||||
if (s.active)
|
||||
t |= bold;
|
||||
if (s.focused)
|
||||
t |= inverted;
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
Add(Container::Vertical({
|
||||
Checkbox(label_, show_.operator->(), opt),
|
||||
Maybe(std::move(child), show_.operator->()),
|
||||
|
@@ -12,6 +12,10 @@
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
|
||||
#include "ftxui/dom/elements.hpp" // for text, Element
|
||||
|
||||
namespace ftxui::animation {
|
||||
class Params;
|
||||
} // namespace ftxui::animation
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
@@ -101,6 +105,15 @@ bool ComponentBase::OnEvent(Event event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @brief Called in response to an animation event.
|
||||
/// @param animation_params the parameters of the animation
|
||||
/// The default implementation dispatch the event to every child.
|
||||
/// @ingroup component
|
||||
void ComponentBase::OnAnimation(animation::Params& params) {
|
||||
for (Component& child : children_)
|
||||
child->OnAnimation(params);
|
||||
}
|
||||
|
||||
/// @brief Return the currently Active child.
|
||||
/// @return the currently Active child.
|
||||
/// @ingroup component
|
||||
|
228
src/ftxui/component/component_options.cpp
Normal file
228
src/ftxui/component/component_options.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "ftxui/component/component_options.hpp"
|
||||
|
||||
#include <memory> // for allocator, shared_ptr
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Function, Duration
|
||||
#include "ftxui/dom/elements.hpp" // for Element, operator|, text, bold, dim, inverted, automerge
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
void AnimatedColorOption::Set(Color a_inactive,
|
||||
Color a_active,
|
||||
animation::Duration a_duration,
|
||||
animation::easing::Function a_function) {
|
||||
enabled = true;
|
||||
inactive = a_inactive;
|
||||
active = a_active;
|
||||
duration = a_duration;
|
||||
function = a_function;
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimation(animation::Duration d,
|
||||
animation::easing::Function f) {
|
||||
SetAnimationDuration(d);
|
||||
SetAnimationFunction(f);
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationDuration(animation::Duration d) {
|
||||
leader_duration = d;
|
||||
follower_duration = d;
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationFunction(animation::easing::Function f) {
|
||||
leader_function = f;
|
||||
follower_function = f;
|
||||
}
|
||||
|
||||
void UnderlineOption::SetAnimationFunction(
|
||||
animation::easing::Function f_leader,
|
||||
animation::easing::Function f_follower) {
|
||||
leader_function = f_leader;
|
||||
follower_function = f_follower;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Horizontal() {
|
||||
MenuOption option;
|
||||
option.direction = Direction::Right;
|
||||
option.entries.transform = [](EntryState state) {
|
||||
Element e = text(state.label);
|
||||
if (state.focused)
|
||||
e |= inverted;
|
||||
if (state.active)
|
||||
e |= bold;
|
||||
if (!state.focused && !state.active)
|
||||
e |= dim;
|
||||
return e;
|
||||
};
|
||||
option.elements_infix = [] { return text(" "); };
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::HorizontalAnimated() {
|
||||
auto option = Horizontal();
|
||||
option.underline.enabled = true;
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Vertical() {
|
||||
MenuOption option;
|
||||
option.entries.transform = [](EntryState state) {
|
||||
if (state.active)
|
||||
state.label = "> " + state.label;
|
||||
else
|
||||
state.label = " " + state.label;
|
||||
|
||||
Element e = text(state.label);
|
||||
if (state.focused)
|
||||
e |= inverted;
|
||||
if (state.active)
|
||||
e |= bold;
|
||||
if (!state.focused && !state.active)
|
||||
e |= dim;
|
||||
return e;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::VerticalAnimated() {
|
||||
auto option = MenuOption::Vertical();
|
||||
option.entries.transform = [](EntryState state) {
|
||||
Element e = text(state.label);
|
||||
if (state.focused)
|
||||
e |= inverted;
|
||||
if (state.active)
|
||||
e |= bold;
|
||||
if (!state.focused && !state.active)
|
||||
e |= dim;
|
||||
return e;
|
||||
};
|
||||
option.underline.enabled = true;
|
||||
return option;
|
||||
}
|
||||
|
||||
// static
|
||||
MenuOption MenuOption::Toggle() {
|
||||
auto option = MenuOption::Horizontal();
|
||||
option.elements_infix = [] { return text("│") | automerge; };
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, highlighted using [] characters.
|
||||
// static
|
||||
ButtonOption ButtonOption::Ascii() {
|
||||
ButtonOption option;
|
||||
option.transform = [](EntryState s) {
|
||||
s.label = s.focused ? "[" + s.label + "]" //
|
||||
: " " + s.label + " ";
|
||||
return text(s.label);
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, inverted when focused.
|
||||
// static
|
||||
ButtonOption ButtonOption::Simple() {
|
||||
ButtonOption option;
|
||||
option.transform = [](EntryState s) {
|
||||
auto element = text(s.label) | borderLight;
|
||||
if (s.focused)
|
||||
element |= inverted;
|
||||
return element;
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated() {
|
||||
return Animated(Color::Black, Color::GrayLight, //
|
||||
Color::GrayDark, Color::White);
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color color) {
|
||||
return ButtonOption::Animated(Color::Interpolate(0.85f, color, Color::Black),
|
||||
Color::Interpolate(0.10f, color, Color::White),
|
||||
Color::Interpolate(0.10f, color, Color::Black),
|
||||
Color::Interpolate(0.85f, color, Color::White));
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color background, Color foreground) {
|
||||
return ButtonOption::Animated(background, foreground, foreground, background);
|
||||
}
|
||||
|
||||
/// @brief Create a ButtonOption, using animated colors.
|
||||
// static
|
||||
ButtonOption ButtonOption::Animated(Color background,
|
||||
Color foreground,
|
||||
Color background_focused,
|
||||
Color foreground_focused) {
|
||||
ButtonOption option;
|
||||
option.transform = [](EntryState s) {
|
||||
auto element = text(s.label) | borderEmpty;
|
||||
if (s.focused)
|
||||
element |= bold;
|
||||
return element;
|
||||
};
|
||||
option.animated_colors.foreground.Set(foreground, foreground_focused);
|
||||
option.animated_colors.background.Set(background, background_focused);
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Option for standard Checkbox.
|
||||
// static
|
||||
CheckboxOption CheckboxOption::Simple() {
|
||||
auto option = CheckboxOption();
|
||||
option.transform = [](EntryState s) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
auto prefix = text(s.state ? "[X] " : "[ ] ");
|
||||
#else
|
||||
auto prefix = text(s.state ? "▣ " : "☐ ");
|
||||
#endif
|
||||
auto t = text(s.label);
|
||||
if (s.active)
|
||||
t |= bold;
|
||||
if (s.focused)
|
||||
t |= inverted;
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
/// @brief Option for standard Radiobox
|
||||
// static
|
||||
RadioboxOption RadioboxOption::Simple() {
|
||||
auto option = RadioboxOption();
|
||||
option.transform = [](EntryState s) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
auto prefix = text(s.state ? "(*) " : "( ) ");
|
||||
#else
|
||||
auto prefix = text(s.state ? "◉ " : "○ ");
|
||||
#endif
|
||||
auto t = text(s.label);
|
||||
if (s.active)
|
||||
t |= bold;
|
||||
if (s.focused)
|
||||
t |= inverted;
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -17,8 +17,15 @@ Component Dropdown(ConstStringListRef entries, int* selected) {
|
||||
Impl(ConstStringListRef entries, int* selected)
|
||||
: entries_(std::move(entries)), selected_(selected) {
|
||||
CheckboxOption option;
|
||||
option.style_checked = "↓ ";
|
||||
option.style_unchecked = "→ ";
|
||||
option.transform = [](EntryState s) {
|
||||
auto prefix = text(s.state ? "↓ " : "→ ");
|
||||
auto t = text(s.label);
|
||||
if (s.active)
|
||||
t |= bold;
|
||||
if (s.focused)
|
||||
t |= inverted;
|
||||
return hbox({prefix, t});
|
||||
};
|
||||
checkbox_ = Checkbox(&title_, &show_, option),
|
||||
radiobox_ = Radiobox(entries_, selected_);
|
||||
|
||||
|
@@ -66,7 +66,7 @@ class InputBase : public ComponentBase {
|
||||
bool hovered = hovered_;
|
||||
Decorator decorator = dim | main_decorator;
|
||||
if (is_focused)
|
||||
decorator = decorator | focus | inverted;
|
||||
decorator = decorator | focus;
|
||||
if (hovered || is_focused)
|
||||
decorator = decorator | inverted;
|
||||
return text(*placeholder_) | decorator | reflect(box_);
|
||||
|
@@ -1,24 +1,65 @@
|
||||
#include <algorithm> // for max
|
||||
#include <algorithm> // for max, reverse
|
||||
#include <chrono> // for milliseconds
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <string> // for operator+, string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type, swap
|
||||
#include <string> // for char_traits, operator+, string, basic_string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector, __alloc_traits<>::value_type
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for Animator, Linear, Params (ptr only)
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, text, nothing, select, vbox, Elements, focus
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, Decorator, nothing, Elements, bgcolor, color, hbox, separatorHSelector, separatorVSelector, vbox, xflex, yflex, text, bold, focus, inverted, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
Element DefaultOptionTransform(EntryState state) {
|
||||
state.label = (state.active ? "> " : " ") + state.label;
|
||||
Element e = text(state.label);
|
||||
if (state.focused)
|
||||
e = e | inverted;
|
||||
if (state.active)
|
||||
e = e | bold;
|
||||
return e;
|
||||
}
|
||||
|
||||
bool IsInverted(MenuOption::Direction direction) {
|
||||
switch (direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
case MenuOption::Direction::Left:
|
||||
return true;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Right:
|
||||
return false;
|
||||
}
|
||||
return false; // NOT_REACHED()
|
||||
}
|
||||
|
||||
bool IsHorizontal(MenuOption::Direction direction) {
|
||||
switch (direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
return true;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
return false;
|
||||
}
|
||||
return false; // NOT_REACHED()
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief A list of items. The user can navigate through them.
|
||||
/// @ingroup component
|
||||
class MenuBase : public ComponentBase {
|
||||
@@ -26,26 +67,148 @@ class MenuBase : public ComponentBase {
|
||||
MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
|
||||
: entries_(entries), selected_(selected), option_(option) {}
|
||||
|
||||
bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); }
|
||||
void OnChange() {
|
||||
if (option_->on_change)
|
||||
option_->on_change();
|
||||
}
|
||||
|
||||
void OnEnter() {
|
||||
if (option_->on_enter)
|
||||
option_->on_enter();
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& params) override {
|
||||
animator_first_.OnAnimation(params);
|
||||
animator_second_.OnAnimation(params);
|
||||
for (auto& animator : animator_background_)
|
||||
animator.OnAnimation(params);
|
||||
for (auto& animator : animator_foreground_)
|
||||
animator.OnAnimation(params);
|
||||
}
|
||||
|
||||
Element Render() override {
|
||||
Clamp();
|
||||
UpdateAnimationTarget();
|
||||
|
||||
Elements elements;
|
||||
bool is_menu_focused = Focused();
|
||||
if (option_->elements_prefix)
|
||||
elements.push_back(option_->elements_prefix());
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
if (i != 0 && option_->elements_infix)
|
||||
elements.push_back(option_->elements_infix());
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
auto icon = is_selected ? "> " : " ";
|
||||
elements.push_back(text(icon + entries_[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
: nothing;
|
||||
EntryState state = {
|
||||
entries_[i],
|
||||
false,
|
||||
is_selected,
|
||||
is_focused,
|
||||
};
|
||||
|
||||
Element element =
|
||||
(option_->entries.transform ? option_->entries.transform
|
||||
: DefaultOptionTransform) //
|
||||
(std::move(state));
|
||||
elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
|
||||
focus_management);
|
||||
}
|
||||
if (option_->elements_postfix)
|
||||
elements.push_back(option_->elements_postfix());
|
||||
|
||||
if (IsInverted(option_->direction))
|
||||
std::reverse(elements.begin(), elements.end());
|
||||
|
||||
Element bar =
|
||||
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
|
||||
|
||||
if (!option_->underline.enabled)
|
||||
return bar | reflect(box_);
|
||||
|
||||
if (IsHorizontal()) {
|
||||
return vbox({
|
||||
bar | xflex,
|
||||
separatorHSelector(first_, second_, //
|
||||
option_->underline.color_active,
|
||||
option_->underline.color_inactive),
|
||||
}) |
|
||||
reflect(box_);
|
||||
} else {
|
||||
return hbox({
|
||||
separatorVSelector(first_, second_, //
|
||||
option_->underline.color_active,
|
||||
option_->underline.color_inactive),
|
||||
bar | yflex,
|
||||
}) |
|
||||
reflect(box_);
|
||||
}
|
||||
}
|
||||
|
||||
void OnUp() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDown() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Up:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Left:
|
||||
case MenuOption::Direction::Right:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnLeft() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Right:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnRight() {
|
||||
switch (option_->direction) {
|
||||
case MenuOption::Direction::Left:
|
||||
(*selected_)--;
|
||||
break;
|
||||
case MenuOption::Direction::Right:
|
||||
(*selected_)++;
|
||||
break;
|
||||
case MenuOption::Direction::Down:
|
||||
case MenuOption::Direction::Up:
|
||||
break;
|
||||
}
|
||||
return vbox(std::move(elements)) | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
@@ -59,9 +222,13 @@ class MenuBase : public ComponentBase {
|
||||
if (Focused()) {
|
||||
int old_selected = *selected_;
|
||||
if (event == Event::ArrowUp || event == Event::Character('k'))
|
||||
(*selected_)--;
|
||||
OnUp();
|
||||
if (event == Event::ArrowDown || event == Event::Character('j'))
|
||||
(*selected_)++;
|
||||
OnDown();
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h'))
|
||||
OnLeft();
|
||||
if (event == Event::ArrowRight || event == Event::Character('l'))
|
||||
OnRight();
|
||||
if (event == Event::PageUp)
|
||||
(*selected_) -= box_.y_max - box_.y_min;
|
||||
if (event == Event::PageDown)
|
||||
@@ -79,13 +246,13 @@ class MenuBase : public ComponentBase {
|
||||
|
||||
if (*selected_ != old_selected) {
|
||||
focused_entry() = *selected_;
|
||||
option_->on_change();
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
option_->on_enter();
|
||||
OnEnter();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -114,7 +281,7 @@ class MenuBase : public ComponentBase {
|
||||
event.mouse().motion == Mouse::Released) {
|
||||
if (*selected_ != i) {
|
||||
*selected_ = i;
|
||||
option_->on_change();
|
||||
OnChange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -135,19 +302,115 @@ class MenuBase : public ComponentBase {
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (*selected_ != old_selected)
|
||||
option_->on_change();
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
void UpdateAnimationTarget() {
|
||||
UpdateColorTarget();
|
||||
UpdateUnderlineTarget();
|
||||
}
|
||||
|
||||
void UpdateColorTarget() {
|
||||
if (size() != (int)animation_background_.size()) {
|
||||
animation_background_.resize(size());
|
||||
animation_foreground_.resize(size());
|
||||
animator_background_.clear();
|
||||
animator_foreground_.clear();
|
||||
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
animation_background_[i] = 0.f;
|
||||
animation_foreground_[i] = 0.f;
|
||||
animator_background_.emplace_back(&animation_background_[i], 0.f,
|
||||
std::chrono::milliseconds(0),
|
||||
animation::easing::Linear);
|
||||
animator_foreground_.emplace_back(&animation_foreground_[i], 0.f,
|
||||
std::chrono::milliseconds(0),
|
||||
animation::easing::Linear);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_menu_focused = Focused();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
float target = is_selected ? 1.f : is_focused ? 0.5f : 0.f;
|
||||
if (animator_background_[i].to() != target) {
|
||||
animator_background_[i] = animation::Animator(
|
||||
&animation_background_[i], target,
|
||||
option_->entries.animated_colors.background.duration,
|
||||
option_->entries.animated_colors.background.function);
|
||||
animator_foreground_[i] = animation::Animator(
|
||||
&animation_foreground_[i], target,
|
||||
option_->entries.animated_colors.foreground.duration,
|
||||
option_->entries.animated_colors.foreground.function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle(int i) {
|
||||
Decorator style = nothing;
|
||||
if (option_->entries.animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_[i],
|
||||
option_->entries.animated_colors.foreground.inactive,
|
||||
option_->entries.animated_colors.foreground.active));
|
||||
}
|
||||
|
||||
if (option_->entries.animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_background_[i],
|
||||
option_->entries.animated_colors.background.inactive,
|
||||
option_->entries.animated_colors.background.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void UpdateUnderlineTarget() {
|
||||
if (!option_->underline.enabled)
|
||||
return;
|
||||
|
||||
if (FirstTarget() == animator_first_.to() &&
|
||||
SecondTarget() == animator_second_.to()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FirstTarget() >= animator_first_.to()) {
|
||||
animator_first_ = animation::Animator(
|
||||
&first_, FirstTarget(), option_->underline.follower_duration,
|
||||
option_->underline.follower_function,
|
||||
option_->underline.follower_delay);
|
||||
|
||||
animator_second_ = animation::Animator(
|
||||
&second_, SecondTarget(), option_->underline.leader_duration,
|
||||
option_->underline.leader_function, option_->underline.leader_delay);
|
||||
} else {
|
||||
animator_first_ = animation::Animator(
|
||||
&first_, FirstTarget(), option_->underline.leader_duration,
|
||||
option_->underline.leader_function, option_->underline.leader_delay);
|
||||
|
||||
animator_second_ = animation::Animator(
|
||||
&second_, SecondTarget(), option_->underline.follower_duration,
|
||||
option_->underline.follower_function,
|
||||
option_->underline.follower_delay);
|
||||
}
|
||||
}
|
||||
|
||||
bool Focusable() const final { return entries_.size(); }
|
||||
int& focused_entry() { return option_->focused_entry(); }
|
||||
int size() const { return entries_.size(); }
|
||||
int FirstTarget() {
|
||||
if (boxes_.size() == 0)
|
||||
return 0;
|
||||
return IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
|
||||
: boxes_[*selected_].y_min - box_.y_min;
|
||||
}
|
||||
int SecondTarget() {
|
||||
if (boxes_.size() == 0)
|
||||
return 0;
|
||||
return IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
|
||||
: boxes_[*selected_].y_max - box_.y_min;
|
||||
}
|
||||
|
||||
protected:
|
||||
ConstStringListRef entries_;
|
||||
@@ -156,6 +419,16 @@ class MenuBase : public ComponentBase {
|
||||
|
||||
std::vector<Box> boxes_;
|
||||
Box box_;
|
||||
|
||||
float first_ = 0.f;
|
||||
float second_ = 0.f;
|
||||
animation::Animator animator_first_ = animation::Animator(&first_, 0.f);
|
||||
animation::Animator animator_second_ = animation::Animator(&second_, 0.f);
|
||||
|
||||
std::vector<animation::Animator> animator_background_;
|
||||
std::vector<animation::Animator> animator_foreground_;
|
||||
std::vector<float> animation_background_;
|
||||
std::vector<float> animation_foreground_;
|
||||
};
|
||||
|
||||
/// @brief A list of text. The focused element is selected.
|
||||
@@ -163,7 +436,6 @@ class MenuBase : public ComponentBase {
|
||||
/// @param selected The index of the currently selected element.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
/// @see MenuBase
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
@@ -192,6 +464,41 @@ Component Menu(ConstStringListRef entries,
|
||||
return Make<MenuBase>(entries, selected, std::move(option));
|
||||
}
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @param entries The list of selectable entries to display.
|
||||
/// @param selected Reference the selected entry.
|
||||
/// @param See also |Menu|.
|
||||
/// @ingroup component
|
||||
Component Toggle(ConstStringListRef entries, int* selected) {
|
||||
return Menu(entries, selected, MenuOption::Toggle());
|
||||
}
|
||||
|
||||
/// @brief A specific menu entry. They can be put into a Container::Vertical to
|
||||
/// form a menu.
|
||||
/// @param label The text drawn representing this element.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto screen = ScreenInteractive::TerminalOutput();
|
||||
/// int selected = 0;
|
||||
/// auto menu = Container::Vertical({
|
||||
/// MenuEntry("entry 1"),
|
||||
/// MenuEntry("entry 2"),
|
||||
/// MenuEntry("entry 3"),
|
||||
/// }, &selected);
|
||||
/// screen.Loop(menu);
|
||||
/// ```
|
||||
///
|
||||
/// ### Output
|
||||
///
|
||||
/// ```bash
|
||||
/// > entry 1
|
||||
/// entry 2
|
||||
/// entry 3
|
||||
/// ```
|
||||
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
@@ -201,15 +508,56 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
private:
|
||||
Element Render() override {
|
||||
bool focused = Focused();
|
||||
auto style =
|
||||
hovered_ ? (focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (focused ? option_->style_focused : option_->style_normal);
|
||||
UpdateAnimationTarget();
|
||||
|
||||
EntryState state = {
|
||||
*label_,
|
||||
hovered_,
|
||||
focused,
|
||||
false,
|
||||
};
|
||||
|
||||
Element element =
|
||||
(option_->transform ? option_->transform : DefaultOptionTransform) //
|
||||
(std::move(state));
|
||||
|
||||
auto focus_management = focused ? select : nothing;
|
||||
auto label = focused ? "> " + (*label_) //
|
||||
: " " + (*label_);
|
||||
return text(label) | style | focus_management | reflect(box_);
|
||||
return element | AnimatedColorStyle() | focus_management | reflect(box_);
|
||||
}
|
||||
|
||||
void UpdateAnimationTarget() {
|
||||
bool focused = Focused();
|
||||
float target = focused ? 1.0f : hovered_ ? 0.5f : 0.0f;
|
||||
if (target == animator_background_.to())
|
||||
return;
|
||||
animator_background_ =
|
||||
animation::Animator(&animation_background_, target,
|
||||
option_->animated_colors.background.duration,
|
||||
option_->animated_colors.background.function);
|
||||
animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, target,
|
||||
option_->animated_colors.foreground.duration,
|
||||
option_->animated_colors.foreground.function);
|
||||
}
|
||||
|
||||
Decorator AnimatedColorStyle() {
|
||||
Decorator style = nothing;
|
||||
if (option_->animated_colors.foreground.enabled) {
|
||||
style = style | color(Color::Interpolate(
|
||||
animation_foreground_,
|
||||
option_->animated_colors.foreground.inactive,
|
||||
option_->animated_colors.foreground.active));
|
||||
}
|
||||
|
||||
if (option_->animated_colors.background.enabled) {
|
||||
style = style | bgcolor(Color::Interpolate(
|
||||
animation_background_,
|
||||
option_->animated_colors.background.inactive,
|
||||
option_->animated_colors.background.active));
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
bool Focusable() const override { return true; }
|
||||
bool OnEvent(Event event) override {
|
||||
if (!event.is_mouse())
|
||||
@@ -228,10 +576,23 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OnAnimation(animation::Params& params) override {
|
||||
animator_background_.OnAnimation(params);
|
||||
animator_foreground_.OnAnimation(params);
|
||||
}
|
||||
|
||||
ConstStringRef label_;
|
||||
Ref<MenuEntryOption> option_;
|
||||
Box box_;
|
||||
bool hovered_ = false;
|
||||
|
||||
float animation_background_ = 0.f;
|
||||
float animation_foreground_ = 0.f;
|
||||
animation::Animator animator_background_ =
|
||||
animation::Animator(&animation_background_, 0.f);
|
||||
animation::Animator animator_foreground_ =
|
||||
animation::Animator(&animation_foreground_, 0.f);
|
||||
};
|
||||
|
||||
return Make<Impl>(std::move(label), std::move(option));
|
||||
|
@@ -29,14 +29,6 @@ class RadioboxBase : public ComponentBase {
|
||||
int* selected,
|
||||
Ref<RadioboxOption> option)
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {
|
||||
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
|
||||
// Microsoft terminal do not use fonts able to render properly the default
|
||||
// radiobox glyph.
|
||||
if (option_->style_checked == "◉ ")
|
||||
option_->style_checked = "(*)";
|
||||
if (option_->style_unchecked == "○ ")
|
||||
option_->style_unchecked = "( )";
|
||||
#endif
|
||||
hovered_ = *selected_;
|
||||
}
|
||||
|
||||
@@ -48,19 +40,21 @@ class RadioboxBase : public ComponentBase {
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
bool is_focused = (focused_entry() == i) && is_menu_focused;
|
||||
bool is_selected = (hovered_ == i);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
auto state = EntryState{
|
||||
entries_[i],
|
||||
*selected_ == i,
|
||||
is_selected,
|
||||
is_focused,
|
||||
};
|
||||
auto element =
|
||||
(option_->transform
|
||||
? option_->transform
|
||||
: RadioboxOption::Simple().transform)(std::move(state));
|
||||
|
||||
const std::string& symbol =
|
||||
*selected_ == i ? option_->style_checked : option_->style_unchecked;
|
||||
elements.push_back(hbox(text(symbol), text(entries_[i]) | style) |
|
||||
focus_management | reflect(boxes_[i]));
|
||||
elements.push_back(element | focus_management | reflect(boxes_[i]));
|
||||
}
|
||||
return vbox(std::move(elements)) | reflect(box_);
|
||||
}
|
||||
|
@@ -1,21 +1,23 @@
|
||||
#include <stdio.h> // for fileno, stdin
|
||||
#include <algorithm> // for copy, max, min
|
||||
#include <chrono> // for operator-, duration, operator>=, milliseconds, time_point, common_type<>::type
|
||||
#include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
|
||||
#include <cstdlib> // for NULL
|
||||
#include <functional> // for function
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
|
||||
#include <stack> // for stack
|
||||
#include <thread> // for thread
|
||||
#include <thread> // for thread, sleep_for
|
||||
#include <type_traits> // for decay_t
|
||||
#include <utility> // for swap, move
|
||||
#include <variant> // for visit
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
|
||||
#include "ftxui/dom/node.hpp" // for Node, Render
|
||||
@@ -45,6 +47,14 @@
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace animation {
|
||||
void RequestAnimationFrame() {
|
||||
auto* screen = ScreenInteractive::Active();
|
||||
if (screen)
|
||||
screen->RequestAnimationFrame();
|
||||
}
|
||||
} // namespace animation
|
||||
|
||||
namespace {
|
||||
|
||||
ScreenInteractive* g_active_screen = nullptr;
|
||||
@@ -236,6 +246,13 @@ class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
std::function<void(void)> callback_;
|
||||
};
|
||||
|
||||
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
while (!*quit) {
|
||||
out->Send(AnimationTask());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(15));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScreenInteractive::ScreenInteractive(int dimx,
|
||||
@@ -276,6 +293,15 @@ void ScreenInteractive::PostEvent(Event event) {
|
||||
Post(event);
|
||||
}
|
||||
|
||||
void ScreenInteractive::RequestAnimationFrame() {
|
||||
if (animation_requested_)
|
||||
return;
|
||||
animation_requested_ = true;
|
||||
auto now = animation::Clock::now();
|
||||
if (now - previous_animation_time >= std::chrono::milliseconds(33))
|
||||
previous_animation_time = now;
|
||||
}
|
||||
|
||||
CapturedMouse ScreenInteractive::CaptureMouse() {
|
||||
if (mouse_captured)
|
||||
return nullptr;
|
||||
@@ -330,6 +356,11 @@ Closure ScreenInteractive::WithRestoredIO(Closure fn) {
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
ScreenInteractive* ScreenInteractive::Active() {
|
||||
return g_active_screen;
|
||||
}
|
||||
|
||||
void ScreenInteractive::Install() {
|
||||
// After uninstalling the new configuration, flush it to the terminal to
|
||||
// ensure it is fully applied:
|
||||
@@ -432,54 +463,85 @@ void ScreenInteractive::Install() {
|
||||
task_sender_ = task_receiver_->MakeSender();
|
||||
event_listener_ =
|
||||
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
||||
animation_listener_ =
|
||||
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
|
||||
}
|
||||
|
||||
void ScreenInteractive::Uninstall() {
|
||||
ExitLoopClosure()();
|
||||
event_listener_.join();
|
||||
animation_listener_.join();
|
||||
|
||||
OnExit(0);
|
||||
}
|
||||
|
||||
void ScreenInteractive::Main(Component component) {
|
||||
previous_animation_time = animation::Clock::now();
|
||||
|
||||
auto draw = [&] {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
};
|
||||
|
||||
draw();
|
||||
|
||||
while (!quit_) {
|
||||
if (!task_receiver_->HasPending()) {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
}
|
||||
if (!task_receiver_->HasPending())
|
||||
draw();
|
||||
|
||||
Task task;
|
||||
if (!task_receiver_->Receive(&task))
|
||||
break;
|
||||
bool continue_event_loop = true;
|
||||
while (continue_event_loop) {
|
||||
continue_event_loop = false;
|
||||
Task task;
|
||||
if (!task_receiver_->Receive(&task))
|
||||
break;
|
||||
|
||||
std::visit(
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
std::visit(
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
if (arg.is_cursor_reporting()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
if (arg.is_cursor_reporting()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.is_mouse()) {
|
||||
arg.mouse().x -= cursor_x_;
|
||||
arg.mouse().y -= cursor_y_;
|
||||
}
|
||||
|
||||
arg.screen_ = this;
|
||||
component->OnEvent(arg);
|
||||
}
|
||||
|
||||
// Handle callback
|
||||
if constexpr (std::is_same_v<T, Closure>) {
|
||||
arg();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.is_mouse()) {
|
||||
arg.mouse().x -= cursor_x_;
|
||||
arg.mouse().y -= cursor_y_;
|
||||
// Handle Animation
|
||||
if constexpr (std::is_same_v<T, AnimationTask>) {
|
||||
if (!animation_requested_) {
|
||||
continue_event_loop = true;
|
||||
return;
|
||||
}
|
||||
animation_requested_ = false;
|
||||
animation::TimePoint now = animation::Clock::now();
|
||||
animation::Duration delta = now - previous_animation_time;
|
||||
previous_animation_time = now;
|
||||
|
||||
animation::Params params(delta);
|
||||
component->OnAnimation(params);
|
||||
}
|
||||
|
||||
arg.screen_ = this;
|
||||
component->OnEvent(arg);
|
||||
}
|
||||
|
||||
// Handle callback
|
||||
if constexpr (std::is_same_v<T, Closure>)
|
||||
arg();
|
||||
},
|
||||
task);
|
||||
},
|
||||
task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,144 +0,0 @@
|
||||
#include <algorithm> // for max
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr, allocator_traits<>::value_type
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
||||
#include "ftxui/component/component.hpp" // for Make, Toggle
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/component/component_options.hpp" // for ToggleOption
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, Elements, hbox, reflect, separator, text, focus, nothing, select
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/util.hpp" // for clamp
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @ingroup component
|
||||
class ToggleBase : public ComponentBase {
|
||||
public:
|
||||
ToggleBase(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<ToggleOption> option)
|
||||
: entries_(entries), selected_(selected), option_(std::move(option)) {}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
Clamp();
|
||||
Elements children;
|
||||
bool is_toggle_focused = Focused();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
// Separator.
|
||||
if (i != 0)
|
||||
children.push_back(separator());
|
||||
|
||||
bool is_focused = (focused_entry() == i) && is_toggle_focused;
|
||||
bool is_selected = (*selected_ == i);
|
||||
|
||||
auto style = is_selected ? (is_focused ? option_->style_selected_focused
|
||||
: option_->style_selected)
|
||||
: (is_focused ? option_->style_focused
|
||||
: option_->style_normal);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_toggle_focused ? focus
|
||||
: select;
|
||||
children.push_back(text(entries_[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
}
|
||||
return hbox(std::move(children));
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
Clamp();
|
||||
if (event.is_mouse())
|
||||
return OnMouseEvent(event);
|
||||
|
||||
int old_selected = *selected_;
|
||||
if (event == Event::ArrowLeft || event == Event::Character('h'))
|
||||
(*selected_)--;
|
||||
if (event == Event::ArrowRight || event == Event::Character('l'))
|
||||
(*selected_)++;
|
||||
if (event == Event::Tab && size())
|
||||
*selected_ = (*selected_ + 1) % size();
|
||||
if (event == Event::TabReverse && size())
|
||||
*selected_ = (*selected_ + size() - 1) % size();
|
||||
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
|
||||
if (old_selected != *selected_) {
|
||||
focused_entry() = *selected_;
|
||||
option_->on_change();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == Event::Return) {
|
||||
option_->on_enter();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OnMouseEvent(Event event) {
|
||||
if (!CaptureMouse(event))
|
||||
return false;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y))
|
||||
continue;
|
||||
|
||||
TakeFocus();
|
||||
focused_entry() = i;
|
||||
if (event.mouse().button == Mouse::Left &&
|
||||
event.mouse().motion == Mouse::Pressed) {
|
||||
TakeFocus();
|
||||
if (*selected_ != i) {
|
||||
*selected_ = i;
|
||||
option_->on_change();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clamp() {
|
||||
boxes_.resize(size());
|
||||
*selected_ = util::clamp(*selected_, 0, size() - 1);
|
||||
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
|
||||
}
|
||||
|
||||
bool Focusable() const final { return size(); }
|
||||
int& focused_entry() { return option_->focused_entry(); }
|
||||
int size() const { return entries_.size(); }
|
||||
|
||||
ConstStringListRef entries_;
|
||||
int* selected_ = 0;
|
||||
|
||||
std::vector<Box> boxes_;
|
||||
Ref<ToggleOption> option_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief An horizontal list of elements. The user can navigate through them.
|
||||
/// @param entries The list of selectable entries to display.
|
||||
/// @param selected Reference the selected entry.
|
||||
/// @param option Additional optional parameters.
|
||||
/// @ingroup component
|
||||
Component Toggle(ConstStringListRef entries,
|
||||
int* selected,
|
||||
Ref<ToggleOption> option) {
|
||||
return Make<ToggleBase>(entries, selected, std::move(option));
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
@@ -90,10 +90,10 @@ TEST(ToggleTest, OnChange) {
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
int selected = 0;
|
||||
int counter = 0;
|
||||
auto option = ToggleOption();
|
||||
auto option = MenuOption::Toggle();
|
||||
option.on_change = [&] { counter++; };
|
||||
|
||||
auto toggle = Toggle(&entries, &selected, &option);
|
||||
auto toggle = Menu(&entries, &selected, &option);
|
||||
|
||||
EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left.
|
||||
EXPECT_EQ(counter, 0);
|
||||
@@ -120,9 +120,9 @@ TEST(ToggleTest, OnEnter) {
|
||||
int selected = 0;
|
||||
int counter = 0;
|
||||
|
||||
auto option = ToggleOption();
|
||||
auto option = MenuOption::Toggle();
|
||||
option.on_enter = [&] { counter++; };
|
||||
auto toggle = Toggle(&entries, &selected, &option);
|
||||
auto toggle = Menu(&entries, &selected, &option);
|
||||
|
||||
EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left.
|
||||
EXPECT_TRUE(toggle->OnEvent(Event::Return));
|
||||
@@ -155,9 +155,9 @@ TEST(ToggleTest, RemoveEntries) {
|
||||
int focused_entry = 0;
|
||||
int selected = 0;
|
||||
std::vector<std::string> entries = {"1", "2", "3"};
|
||||
ToggleOption option;
|
||||
auto option = MenuOption::Toggle();
|
||||
option.focused_entry = &focused_entry;
|
||||
auto toggle = Toggle(&entries, &selected, option);
|
||||
auto toggle = Menu(&entries, &selected, option);
|
||||
|
||||
EXPECT_EQ(selected, 0);
|
||||
EXPECT_EQ(focused_entry, 0);
|
||||
|
@@ -1,9 +1,7 @@
|
||||
#include <functional> // for function
|
||||
#include <memory> // for __shared_ptr_access
|
||||
|
||||
#include "ftxui/component/component.hpp" // for ElementDecorator, Renderer, ComponentDecorator, operator|, operator|=
|
||||
#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
|
||||
#include "ftxui/dom/elements.hpp" // for Element
|
||||
#include "ftxui/component/component.hpp" // for Renderer, ComponentDecorator, ElementDecorator, operator|, operator|=
|
||||
#include "ftxui/component/component_base.hpp" // for Component
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
|
@@ -1,10 +1,11 @@
|
||||
#include <memory> // for make_shared
|
||||
#include <memory> // for make_shared, allocator
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for Element, separator
|
||||
#include "ftxui/dom/node.hpp" // for Node
|
||||
#include "ftxui/dom/elements.hpp" // for Element, BorderStyle, LIGHT, separator, DOUBLE, EMPTY, HEAVY, separatorCharacter, separatorDouble, separatorEmpty, separatorHSelector, separatorHeavy, separatorLight, separatorStyled, separatorVSelector
|
||||
#include "ftxui/dom/node.hpp" // for Node
|
||||
#include "ftxui/dom/requirement.hpp" // for Requirement
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/screen/screen.hpp" // for Pixel, Screen
|
||||
|
||||
namespace ftxui {
|
||||
@@ -369,6 +370,142 @@ Element separator(Pixel pixel) {
|
||||
return std::make_shared<SeparatorWithPixel>(pixel);
|
||||
}
|
||||
|
||||
/// @brief Draw an horizontal bar, with the area in between left/right colored
|
||||
/// differently.
|
||||
/// @param left the left limit of the active area.
|
||||
/// @param right the right limit of the active area.
|
||||
/// @param selected_color the color of the selected area.
|
||||
/// @param unselected_color the color of the unselected area.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// Element document = separatorHSelector(2,5, Color::White, Color::Blue);
|
||||
/// ```
|
||||
Element separatorHSelector(float left,
|
||||
float right,
|
||||
Color selected_color,
|
||||
Color unselected_color) {
|
||||
class Impl : public Node {
|
||||
public:
|
||||
Impl(float left, float right, Color selected_color, Color unselected_color)
|
||||
: left_(left),
|
||||
right_(right),
|
||||
selected_color_(selected_color),
|
||||
unselected_color_(unselected_color) {}
|
||||
void ComputeRequirement() override {
|
||||
requirement_.min_x = 1;
|
||||
requirement_.min_y = 1;
|
||||
}
|
||||
|
||||
void Render(Screen& screen) override {
|
||||
if (box_.y_max < box_.y_min)
|
||||
return;
|
||||
|
||||
// This are the two location with an empty demi-cell.
|
||||
int demi_cell_left = left_ * 2 - 1;
|
||||
int demi_cell_right = right_ * 2 + 2;
|
||||
|
||||
int y = box_.y_min;
|
||||
for (int x = box_.x_min; x <= box_.x_max; ++x) {
|
||||
Pixel& pixel = screen.PixelAt(x, y);
|
||||
|
||||
int a = (x - box_.x_min) * 2;
|
||||
int b = a + 1;
|
||||
bool a_empty = demi_cell_left == a || demi_cell_right == a;
|
||||
bool b_empty = demi_cell_left == b || demi_cell_right == b;
|
||||
|
||||
if (!a_empty && !b_empty) {
|
||||
pixel.character = "─";
|
||||
pixel.automerge = true;
|
||||
} else {
|
||||
pixel.character = a_empty ? "╶" : "╴";
|
||||
pixel.automerge = false;
|
||||
}
|
||||
|
||||
if (demi_cell_left <= a && b <= demi_cell_right)
|
||||
pixel.foreground_color = selected_color_;
|
||||
else
|
||||
pixel.foreground_color = unselected_color_;
|
||||
}
|
||||
}
|
||||
|
||||
float left_;
|
||||
float right_;
|
||||
Color selected_color_;
|
||||
Color unselected_color_;
|
||||
};
|
||||
return std::make_shared<Impl>(left, right, selected_color, unselected_color);
|
||||
}
|
||||
|
||||
/// @brief Draw an vertical bar, with the area in between up/downcolored
|
||||
/// differently.
|
||||
/// @param up the left limit of the active area.
|
||||
/// @param down the right limit of the active area.
|
||||
/// @param selected_color the color of the selected area.
|
||||
/// @param unselected_color the color of the unselected area.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// Element document = separatorHSelector(2,5, Color::White, Color::Blue);
|
||||
/// ```
|
||||
Element separatorVSelector(float up,
|
||||
float down,
|
||||
Color selected_color,
|
||||
Color unselected_color) {
|
||||
class Impl : public Node {
|
||||
public:
|
||||
Impl(float up, float down, Color selected_color, Color unselected_color)
|
||||
: up_(up),
|
||||
down_(down),
|
||||
selected_color_(selected_color),
|
||||
unselected_color_(unselected_color) {}
|
||||
void ComputeRequirement() override {
|
||||
requirement_.min_x = 1;
|
||||
requirement_.min_y = 1;
|
||||
}
|
||||
|
||||
void Render(Screen& screen) override {
|
||||
if (box_.x_max < box_.x_min)
|
||||
return;
|
||||
|
||||
// This are the two location with an empty demi-cell.
|
||||
int demi_cell_up = up_ * 2 - 1;
|
||||
int demi_cell_down = down_ * 2 + 2;
|
||||
|
||||
int x = box_.x_min;
|
||||
for (int y = box_.y_min; y <= box_.y_max; ++y) {
|
||||
Pixel& pixel = screen.PixelAt(x, y);
|
||||
|
||||
int a = (y - box_.y_min) * 2;
|
||||
int b = a + 1;
|
||||
bool a_empty = demi_cell_up == a || demi_cell_down == a;
|
||||
bool b_empty = demi_cell_up == b || demi_cell_down == b;
|
||||
|
||||
if (!a_empty && !b_empty) {
|
||||
pixel.character = "│";
|
||||
pixel.automerge = true;
|
||||
} else {
|
||||
pixel.character = a_empty ? "╷" : "╵";
|
||||
pixel.automerge = false;
|
||||
}
|
||||
|
||||
if (demi_cell_up <= a && b <= demi_cell_down)
|
||||
pixel.foreground_color = selected_color_;
|
||||
else
|
||||
pixel.foreground_color = unselected_color_;
|
||||
}
|
||||
}
|
||||
|
||||
float up_;
|
||||
float down_;
|
||||
Color selected_color_;
|
||||
Color unselected_color_;
|
||||
};
|
||||
return std::make_shared<Impl>(up, down, selected_color, unselected_color);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -145,6 +145,77 @@ Color Color::HSV(uint8_t h, uint8_t s, uint8_t v) {
|
||||
return Color(0, 0, 0);
|
||||
}
|
||||
|
||||
// static
|
||||
Color Color::Interpolate(float t, const Color& a, const Color& b) {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
switch (a.type_) {
|
||||
case ColorType::Palette1: {
|
||||
if (t < 0.5)
|
||||
return a;
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
case ColorType::Palette16: {
|
||||
ColorInfo info = GetColorInfo(Color::Palette16(a.index_));
|
||||
red = info.red * (1 - t);
|
||||
green = info.green * (1 - t);
|
||||
blue = info.blue * (1 - t);
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorType::Palette256: {
|
||||
ColorInfo info = GetColorInfo(Color::Palette256(a.index_));
|
||||
red = info.red * (1 - t);
|
||||
green = info.green * (1 - t);
|
||||
blue = info.blue * (1 - t);
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorType::TrueColor: {
|
||||
red = a.red_ * (1 - t);
|
||||
green = a.green_ * (1 - t);
|
||||
blue = a.blue_ * (1 - t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (b.type_) {
|
||||
case ColorType::Palette1: {
|
||||
if (t > 0.5)
|
||||
return a;
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
case ColorType::Palette16: {
|
||||
ColorInfo info = GetColorInfo(Color::Palette16(b.index_));
|
||||
red += info.red * t;
|
||||
green += info.green * t;
|
||||
blue += info.blue * t;
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorType::Palette256: {
|
||||
ColorInfo info = GetColorInfo(Color::Palette256(b.index_));
|
||||
red += info.red * t;
|
||||
green += info.green * t;
|
||||
blue += info.blue * t;
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorType::TrueColor: {
|
||||
red += b.red_ * t;
|
||||
green += b.green_ * t;
|
||||
blue += b.blue_ * t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Color::RGB(red, green, blue);
|
||||
}
|
||||
|
||||
inline namespace literals {
|
||||
|
||||
Color operator""_rgb(unsigned long long int combined) {
|
||||
|
Reference in New Issue
Block a user