Support multiple kind of cursor shapes. (#505)

https://github.com/ArthurSonzogni/FTXUI/issues/424
This commit is contained in:
Arthur Sonzogni (slow/sick) 2022-11-11 14:09:53 +01:00 committed by GitHub
parent 9babfea36b
commit 1689802349
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 32 deletions

View File

@ -5,6 +5,13 @@ current (development)
---------------------
### DOM
- Feature: Customize the cursor. Add the following decorators:
- `focusCursorBlock`
- `focusCursorBlockBlinking`
- `focusCursorBar`
- `focusCursorBarBlinking`
- `focusCursorUnderline`
- `focusCursorUnderlineBlinking`
- Bugfix: Fix `focus`/`select` when the `vbox`/`hbox`/`dbox` contains a
`flexbox`
- Bugfix: Fix the selected/focused area. It used to be 1 cell larger/longer than
@ -25,6 +32,7 @@ current (development)
can be used to integrate FTXUI into another main loop, without taking the full
control.
- Feature: `Input` supports CTRL+Left and CTRL+Right
- Feature: Use a blinking bar in the `Input` component.
- Improvement: The `Menu` keeps the focus when an entry is selected with the
mouse.
- Bugfix: Add implementation of `ButtonOption::Border()`. It was missing.

View File

@ -13,6 +13,7 @@ example(custom_loop)
example(dropdown)
example(flexbox_gallery)
example(focus)
example(focus_cursor)
example(gallery)
example(homescreen)
example(input)

View File

@ -0,0 +1,39 @@
#include <memory> // for allocator, shared_ptr, __shared_ptr_access
#include <string> // for operator+, char_traits, to_string, string
#include <vector> // for vector
#include "ftxui/component/component.hpp" // for Slider, Renderer, Vertical
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for Elements, Element, operator|, separator, text, focusPositionRelative, size, border, flex, frame, bgcolor, gridbox, vbox, EQUAL, center, HEIGHT, WIDTH
using namespace ftxui;
Component Instance(std::string label, Decorator focusCursor) {
return Renderer([=](bool focused) {
if (focused) {
return hbox({
text("> " + label + " "),
focusCursor(text(" ")),
});
}
return text(" " + label + " ");
});
};
int main(int argc, const char* argv[]) {
auto screen = ScreenInteractive::Fullscreen();
screen.Loop(Container::Vertical({
Instance("focus", focus),
Instance("focusCursorBlock", focusCursorBlock),
Instance("focusCursorBlockBlinking", focusCursorBlockBlinking),
Instance("focusCursorBar", focusCursorBar),
Instance("focusCursorBarBlinking", focusCursorBarBlinking),
Instance("focusCursorUnderline", focusCursorUnderline),
Instance("focusCursorUnderlineBlinking", focusCursorUnderlineBlinking),
}));
return 0;
}
// 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.

View File

@ -126,9 +126,6 @@ enum Direction { WIDTH, HEIGHT };
enum Constraint { LESS_THAN, EQUAL, GREATER_THAN };
Decorator size(Direction, Constraint, int value);
// --
Decorator reflect(Box& box);
// --- Frame ---
// A frame is a scrollable area. The internal area is potentially larger than
// the external one. The internal area is scrolled in order to make visible the
@ -139,7 +136,21 @@ Element yframe(Element);
Element focus(Element);
Element select(Element);
// --- Cursor ---
// Those are similar to `focus`, but also change the shape of the cursor.
Element focusCursorBlock(Element);
Element focusCursorBlockBlinking(Element);
Element focusCursorBar(Element);
Element focusCursorBarBlinking(Element);
Element focusCursorUnderline(Element);
Element focusCursorUnderlineBlinking(Element);
// --- Misc ---
Element vscroll_indicator(Element);
Decorator reflect(Box& box);
// Before drawing the |element| clear the pixel below. This is useful in
// combinaison with dbox.
Element clear_under(Element element);
// --- Util --------------------------------------------------------------------
Element hcenter(Element);
@ -148,10 +159,6 @@ Element center(Element);
Element align_right(Element);
Element nothing(Element element);
// Before drawing the |element| clear the pixel below. This is useful in
// combinaison with dbox.
Element clear_under(Element element);
namespace Dimension {
Dimensions Fit(Element&);
} // namespace Dimension

View File

@ -80,6 +80,17 @@ class Screen {
struct Cursor {
int x = 0;
int y = 0;
enum Shape {
Hidden = 0,
BlockBlinking = 1,
Block = 2,
UnderlineBlinking = 3,
Underline = 4,
Bar = 5,
BarBlinking = 6,
};
Shape shape;
};
Cursor cursor() const { return cursor_; }
void SetCursor(Cursor cursor) { cursor_ = cursor; }
@ -91,8 +102,6 @@ class Screen {
int dimy_;
std::vector<std::vector<Pixel>> pixels_;
Cursor cursor_;
private:
};
} // namespace ftxui

View File

@ -96,24 +96,23 @@ class InputBase : public ComponentBase {
// placeholder.
if (size == 0) {
bool hovered = hovered_;
Decorator decorator = dim | main_decorator;
auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
if (is_focused) {
decorator = decorator | focus;
element |= focus;
}
if (hovered || is_focused) {
decorator = decorator | inverted;
if (hovered_|| is_focused) {
element |= inverted;
}
return text(*placeholder_) | decorator | reflect(box_);
return element;
}
// Not focused.
if (!is_focused) {
auto element = text(content) | main_decorator | reflect(box_);
if (hovered_) {
return text(content) | main_decorator | inverted | reflect(box_);
} else {
return text(content) | main_decorator | reflect(box_);
}
element |= inverted;
}
return element;
}
int index_before_cursor = GlyphPosition(content, cursor_position());
@ -125,10 +124,10 @@ class InputBase : public ComponentBase {
index_after_cursor - index_before_cursor);
}
std::string part_after_cursor = content.substr(index_after_cursor);
auto focused = (is_focused || hovered_) ? focus : select;
auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select;
return hbox({
text(part_before_cursor),
text(part_at_cursor) | focused | inverted | reflect(cursor_box_),
text(part_at_cursor) | focused | reflect(cursor_box_),
text(part_after_cursor),
}) |
flex | frame | bold | main_decorator | reflect(box_);

View File

@ -512,8 +512,13 @@ void ScreenInteractive::Install() {
});
}
on_exit_functions.push([=] {
std::cout << "\033[?25h"; // Enable cursor.
std::cout << "\033[?1 q"; // Cursor block blinking.
});
disable({
DECMode::kCursor,
//DECMode::kCursor,
DECMode::kLineWrap,
});
@ -685,16 +690,26 @@ void ScreenInteractive::Draw(Component component) {
set_cursor_position = "";
reset_cursor_position = "";
int dx = dimx_ - 1 - cursor_.x;
int dy = dimy_ - 1 - cursor_.y;
{
int dx = dimx_ - 1 - cursor_.x;
int dy = dimy_ - 1 - cursor_.y;
if (dx != 0) {
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
}
if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
}
if (dx != 0) {
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
}
if (cursor_.shape == Cursor::Hidden) {
set_cursor_position += "\033[?25l";
} else {
set_cursor_position += "\033[?25h";
set_cursor_position += "\033[" + std::to_string(int(cursor_.shape)) + " q";
}
}
std::cout << ToString() << set_cursor_position;

View File

@ -71,7 +71,11 @@ class Focus : public Select {
// https://github.com/microsoft/terminal/issues/1203
// https://github.com/microsoft/terminal/issues/3093
#if !defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
screen.SetCursor(Screen::Cursor{box_.x_min, box_.y_min});
screen.SetCursor(Screen::Cursor{
box_.x_min,
box_.y_min,
Screen::Cursor::Shape::Hidden,
});
#endif
}
};
@ -147,6 +151,48 @@ Element yframe(Element child) {
return std::make_shared<Frame>(unpack(std::move(child)), false, true);
}
class FocusCursor : public Focus {
public:
FocusCursor(Elements children, Screen::Cursor::Shape shape)
: Focus(std::move(children)), shape_(shape) {}
private:
void Render(Screen& screen) override {
Select::Render(screen);
screen.SetCursor(Screen::Cursor{
box_.x_min,
box_.y_min,
shape_,
});
}
Screen::Cursor::Shape shape_;
};
Element focusCursorBlock(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::Block);
}
Element focusCursorBlockBlinking(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::BlockBlinking);
}
Element focusCursorBar(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::Bar);
}
Element focusCursorBarBlinking(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::BarBlinking);
}
Element focusCursorUnderline(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::Underline);
}
Element focusCursorUnderlineBlinking(Element child) {
return std::make_shared<FocusCursor>(unpack(std::move(child)),
Screen::Cursor::UnderlineBlinking);
}
} // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved.