diff --git a/CHANGELOG.md b/CHANGELOG.md index b594d5b..f4a678f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 4eb971e..5b618d2 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -13,6 +13,7 @@ example(custom_loop) example(dropdown) example(flexbox_gallery) example(focus) +example(focus_cursor) example(gallery) example(homescreen) example(input) diff --git a/examples/component/focus_cursor.cpp b/examples/component/focus_cursor.cpp new file mode 100644 index 0000000..4bc6a1e --- /dev/null +++ b/examples/component/focus_cursor.cpp @@ -0,0 +1,39 @@ +#include // for allocator, shared_ptr, __shared_ptr_access +#include // for operator+, char_traits, to_string, string +#include // 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. diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index 4d690a4..ce14801 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -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 diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 21eb377..4d15b95 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -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> pixels_; Cursor cursor_; - - private: }; } // namespace ftxui diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index d56ce4b..66a4245 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -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_); diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 278c53c..657487d 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -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; diff --git a/src/ftxui/dom/frame.cpp b/src/ftxui/dom/frame.cpp index 575bbc8..8aff60d 100644 --- a/src/ftxui/dom/frame.cpp +++ b/src/ftxui/dom/frame.cpp @@ -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(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(unpack(std::move(child)), + Screen::Cursor::Block); +} +Element focusCursorBlockBlinking(Element child) { + return std::make_shared(unpack(std::move(child)), + Screen::Cursor::BlockBlinking); +} +Element focusCursorBar(Element child) { + return std::make_shared(unpack(std::move(child)), + Screen::Cursor::Bar); +} +Element focusCursorBarBlinking(Element child) { + return std::make_shared(unpack(std::move(child)), + Screen::Cursor::BarBlinking); +} +Element focusCursorUnderline(Element child) { + return std::make_shared(unpack(std::move(child)), + Screen::Cursor::Underline); +} +Element focusCursorUnderlineBlinking(Element child) { + return std::make_shared(unpack(std::move(child)), + Screen::Cursor::UnderlineBlinking); +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved.