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 ### 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 - Bugfix: Fix `focus`/`select` when the `vbox`/`hbox`/`dbox` contains a
`flexbox` `flexbox`
- Bugfix: Fix the selected/focused area. It used to be 1 cell larger/longer than - 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 can be used to integrate FTXUI into another main loop, without taking the full
control. control.
- Feature: `Input` supports CTRL+Left and CTRL+Right - 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 - Improvement: The `Menu` keeps the focus when an entry is selected with the
mouse. mouse.
- Bugfix: Add implementation of `ButtonOption::Border()`. It was missing. - Bugfix: Add implementation of `ButtonOption::Border()`. It was missing.

View File

@ -13,6 +13,7 @@ example(custom_loop)
example(dropdown) example(dropdown)
example(flexbox_gallery) example(flexbox_gallery)
example(focus) example(focus)
example(focus_cursor)
example(gallery) example(gallery)
example(homescreen) example(homescreen)
example(input) 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 }; enum Constraint { LESS_THAN, EQUAL, GREATER_THAN };
Decorator size(Direction, Constraint, int value); Decorator size(Direction, Constraint, int value);
// --
Decorator reflect(Box& box);
// --- Frame --- // --- Frame ---
// A frame is a scrollable area. The internal area is potentially larger than // 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 // 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 focus(Element);
Element select(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); 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 -------------------------------------------------------------------- // --- Util --------------------------------------------------------------------
Element hcenter(Element); Element hcenter(Element);
@ -148,10 +159,6 @@ Element center(Element);
Element align_right(Element); Element align_right(Element);
Element nothing(Element 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 { namespace Dimension {
Dimensions Fit(Element&); Dimensions Fit(Element&);
} // namespace Dimension } // namespace Dimension

View File

@ -80,6 +80,17 @@ class Screen {
struct Cursor { struct Cursor {
int x = 0; int x = 0;
int y = 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_; } Cursor cursor() const { return cursor_; }
void SetCursor(Cursor cursor) { cursor_ = cursor; } void SetCursor(Cursor cursor) { cursor_ = cursor; }
@ -91,8 +102,6 @@ class Screen {
int dimy_; int dimy_;
std::vector<std::vector<Pixel>> pixels_; std::vector<std::vector<Pixel>> pixels_;
Cursor cursor_; Cursor cursor_;
private:
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -96,24 +96,23 @@ class InputBase : public ComponentBase {
// placeholder. // placeholder.
if (size == 0) { if (size == 0) {
bool hovered = hovered_; auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
Decorator decorator = dim | main_decorator;
if (is_focused) { if (is_focused) {
decorator = decorator | focus; element |= focus;
} }
if (hovered || is_focused) { if (hovered_|| is_focused) {
decorator = decorator | inverted; element |= inverted;
} }
return text(*placeholder_) | decorator | reflect(box_); return element;
} }
// Not focused. // Not focused.
if (!is_focused) { if (!is_focused) {
auto element = text(content) | main_decorator | reflect(box_);
if (hovered_) { if (hovered_) {
return text(content) | main_decorator | inverted | reflect(box_); element |= inverted;
} else { }
return text(content) | main_decorator | reflect(box_); return element;
}
} }
int index_before_cursor = GlyphPosition(content, cursor_position()); int index_before_cursor = GlyphPosition(content, cursor_position());
@ -125,10 +124,10 @@ class InputBase : public ComponentBase {
index_after_cursor - index_before_cursor); index_after_cursor - index_before_cursor);
} }
std::string part_after_cursor = content.substr(index_after_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({ return hbox({
text(part_before_cursor), 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), text(part_after_cursor),
}) | }) |
flex | frame | bold | main_decorator | reflect(box_); 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({ disable({
DECMode::kCursor, //DECMode::kCursor,
DECMode::kLineWrap, DECMode::kLineWrap,
}); });
@ -685,16 +690,26 @@ void ScreenInteractive::Draw(Component component) {
set_cursor_position = ""; set_cursor_position = "";
reset_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) { if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dx) + "D"; set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C"; reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
} }
if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dy) + "A"; if (dx != 0) {
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B"; 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; 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/1203
// https://github.com/microsoft/terminal/issues/3093 // https://github.com/microsoft/terminal/issues/3093
#if !defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK) #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 #endif
} }
}; };
@ -147,6 +151,48 @@ Element yframe(Element child) {
return std::make_shared<Frame>(unpack(std::move(child)), false, true); 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.