Skip to content

Commit

Permalink
Feature: Selection
Browse files Browse the repository at this point in the history
Add support for selection content in the dom.
  • Loading branch information
clement-roblot authored Dec 27, 2024
1 parent 751c8fa commit 6fafa2d
Show file tree
Hide file tree
Showing 25 changed files with 1,001 additions and 31 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ current (development)
- Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
option. Added by @mingsheng13.
- Feature: Add `DropdownOption` to configure the dropdown. See #826.
- Feature: Add support for Selection. Thanks @clement-roblot. See #926.
- See `ScreenInteractive::GetSelection()`.
- See `ScreenInteractive::SelectionChange(...)` listener.
- Bugfix/Breaking change: `Mouse transition`:
- Detect when the mouse move, as opposed to being pressed.
The Mouse::Moved motion was added.
Expand Down Expand Up @@ -49,6 +52,12 @@ current (development)
- Feature: Add `extend_beyond_screen` option to `Dimension::Fit(..)`, allowing
the element to be larger than the screen. Proposed by @LordWhiro. See #572 and
#949.
- Feature: Add support for Selection. Thanks @clement-roblot. See #926.
- See `selectionColor` decorator.
- See `selectionBackgroundColor` decorator.
- See `selectionForegroundColor` decorator.
- See `selectionStyle(style)` decorator.
- See `selectionStyleReset` decorator.

### Screen
- Feature: Add `Box::IsEmpty()`.
Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ add_library(dom
include/ftxui/dom/flexbox_config.hpp
include/ftxui/dom/node.hpp
include/ftxui/dom/requirement.hpp
include/ftxui/dom/selection.hpp
include/ftxui/dom/take_any_args.hpp
src/ftxui/dom/automerge.cpp
src/ftxui/dom/selection_style.cpp
src/ftxui/dom/blink.cpp
src/ftxui/dom/bold.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/border.cpp
src/ftxui/dom/box_helper.cpp
src/ftxui/dom/box_helper.hpp
Expand All @@ -81,13 +82,15 @@ add_library(dom
src/ftxui/dom/graph.cpp
src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/inverted.cpp
src/ftxui/dom/linear_gradient.cpp
src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp
src/ftxui/dom/paragraph.cpp
src/ftxui/dom/reflect.cpp
src/ftxui/dom/scroll_indicator.cpp
src/ftxui/dom/selection.cpp
src/ftxui/dom/separator.cpp
src/ftxui/dom/size.cpp
src/ftxui/dom/spinner.cpp
Expand Down
1 change: 1 addition & 0 deletions cmake/ftxui_test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_executable(ftxui-tests
src/ftxui/dom/hyperlink_test.cpp
src/ftxui/dom/linear_gradient_test.cpp
src/ftxui/dom/scroll_indicator_test.cpp
src/ftxui/dom/selection_test.cpp
src/ftxui/dom/separator_test.cpp
src/ftxui/dom/spinner_test.cpp
src/ftxui/dom/table_test.cpp
Expand Down
1 change: 1 addition & 0 deletions examples/component/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ example(radiobox)
example(radiobox_in_frame)
example(renderer)
example(resizable_split)
example(selection)
example(scrollbar)
example(slider)
example(slider_direction)
Expand Down
23 changes: 22 additions & 1 deletion examples/component/gallery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,25 @@ int main() {
});
sliders = Wrap("Slider", sliders);

// -- Layout -----------------------------------------------------------------
// A large text:
auto lorel_ipsum = Renderer([] {
return vbox({
text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. "),
text("Sed do eiusmod tempor incididunt ut labore et dolore magna "
"aliqua. "),
text("Ut enim ad minim veniam, quis nostrud exercitation ullamco "
"laboris nisi ut aliquip ex ea commodo consequat. "),
text("Duis aute irure dolor in reprehenderit in voluptate velit esse "
"cillum dolore eu fugiat nulla pariatur. "),
text("Excepteur sint occaecat cupidatat non proident, sunt in culpa "
"qui officia deserunt mollit anim id est laborum. "),

});
});
lorel_ipsum = Wrap("Lorel Ipsum", lorel_ipsum);

// -- Layout
// -----------------------------------------------------------------
auto layout = Container::Vertical({
menu,
toggle,
Expand All @@ -106,6 +124,7 @@ int main() {
input,
sliders,
button,
lorel_ipsum,
});

auto component = Renderer(layout, [&] {
Expand All @@ -123,6 +142,8 @@ int main() {
sliders->Render(),
separator(),
button->Render(),
separator(),
lorel_ipsum->Render(),
}) |
xflex | size(WIDTH, GREATER_THAN, 40) | border;
});
Expand Down
2 changes: 1 addition & 1 deletion examples/component/homescreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ int main() {
auto paragraph_renderer_left = Renderer([&] {
std::string str =
"Lorem Ipsum is simply dummy text of the printing and typesetting "
"industry. Lorem Ipsum has been the industry's standard dummy text "
"industry.\nLorem Ipsum has been the industry's standard dummy text "
"ever since the 1500s, when an unknown printer took a galley of type "
"and scrambled it to make a type specimen book.";
return vbox({
Expand Down
87 changes: 87 additions & 0 deletions examples/component/selection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.
#include <string> // for char_traits, operator+, string, basic_string

#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border
#include "ftxui/util/ref.hpp" // for Ref

using namespace ftxui;

Element LoremIpsum() {
return vbox({
text("FTXUI: A powerful library for building user interfaces."),
text("Enjoy a rich set of components and a declarative style."),
text("Create beautiful and responsive UIs with minimal effort."),
text("Join the community and experience the power of FTXUI."),
});
}

int main() {
auto screen = ScreenInteractive::TerminalOutput();

auto quit =
Button("Quit", screen.ExitLoopClosure(), ButtonOption::Animated());

int selection_change_counter = 0;
std::string selection_content = "";
screen.SelectionChange([&] {
selection_change_counter++;
selection_content = screen.GetSelection();
});

// The components:
auto renderer = Renderer(quit, [&] {
return vbox({
text("Select changed: " + std::to_string(selection_change_counter) +
" times"),
text("Currently selected: "),
paragraph(selection_content) | vscroll_indicator | frame | border |
size(HEIGHT, EQUAL, 10),
window(text("Horizontal split"), hbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Vertical split"), vbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Grid split with different style"),
vbox({
hbox({
LoremIpsum(),
separator(),
LoremIpsum() //
| selectionBackgroundColor(Color::Yellow) //
| selectionColor(Color::Black) //
| selectionStyleReset,
separator(),
LoremIpsum() | selectionColor(Color::Blue),
}),
separator(),
hbox({
LoremIpsum() | selectionColor(Color::Red),
separator(),
LoremIpsum() | selectionStyle([](Pixel& pixel) {
pixel.underlined_double = true;
}),
separator(),
LoremIpsum(),
}),
})),
quit->Render(),
});
});

screen.Loop(renderer);
}
23 changes: 23 additions & 0 deletions include/ftxui/component/screen_interactive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/task.hpp" // for Task, Closure
#include "ftxui/dom/selection.hpp" // for SelectionOption
#include "ftxui/screen/screen.hpp" // for Screen

namespace ftxui {
Expand Down Expand Up @@ -68,6 +69,10 @@ class ScreenInteractive : public Screen {
void ForceHandleCtrlC(bool force);
void ForceHandleCtrlZ(bool force);

// Selection API.
std::string GetSelection();
void SelectionChange(std::function<void()> callback);

private:
void ExitNow();

Expand All @@ -82,6 +87,8 @@ class ScreenInteractive : public Screen {
void RunOnceBlocking(Component component);

void HandleTask(Component component, Task& task);
bool HandleSelection(bool handled, Event event);
void RefreshSelection();
void Draw(Component component);
void ResetCursorPosition();

Expand Down Expand Up @@ -129,6 +136,22 @@ class ScreenInteractive : public Screen {
// The style of the cursor to restore on exit.
int cursor_reset_shape_ = 1;

// Selection API:
CapturedMouse selection_pending_;
struct SelectionData {
int start_x = -1;
int start_y = -1;
int end_x = -2;
int end_y = -2;
bool empty = true;
bool operator==(const SelectionData& other) const;
bool operator!=(const SelectionData& other) const;
};
SelectionData selection_data_;
SelectionData selection_data_previous_;
std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_;

friend class Loop;

public:
Expand Down
5 changes: 5 additions & 0 deletions include/ftxui/dom/elements.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ Decorator focusPositionRelative(float x, float y);
Element automerge(Element child);
Decorator hyperlink(std::string link);
Element hyperlink(std::string link, Element child);
Element selectionStyleReset(Element);
Decorator selectionColor(Color foreground);
Decorator selectionBackgroundColor(Color foreground);
Decorator selectionForegroundColor(Color foreground);
Decorator selectionStyle(std::function<void(Pixel&)> style);

// --- Layout is
// Horizontal, Vertical or stacked set of elements.
Expand Down
13 changes: 12 additions & 1 deletion include/ftxui/dom/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <vector> // for vector

#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp"

Expand Down Expand Up @@ -40,9 +41,15 @@ class Node {
// Propagated from Parents to Children.
virtual void SetBox(Box box);

// Step 3: Draw this element.
// Step 3: (optional) Selection
// Propagated from Parents to Children.
virtual void Select(Selection& selection);

// Step 4: Draw this element.
virtual void Render(Screen& screen);

virtual std::string GetSelectedContent(Selection& selection);

// Layout may not resolve within a single iteration for some elements. This
// allows them to request additionnal iterations. This signal must be
// forwarded to children at least once.
Expand All @@ -60,6 +67,10 @@ class Node {

void Render(Screen& screen, const Element& element);
void Render(Screen& screen, Node* node);
void Render(Screen& screen, Node* node, Selection& selection);
std::string GetNodeSelectedContent(Screen& screen,
Node* node,
Selection& selection);

} // namespace ftxui

Expand Down
50 changes: 50 additions & 0 deletions include/ftxui/dom/selection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

#ifndef FTXUI_DOM_SELECTION_HPP
#define FTXUI_DOM_SELECTION_HPP

#include <functional>

#include <sstream>
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/pixel.hpp" // for Pixel

namespace ftxui {

/// @brief Represent a selection in the terminal.
class Selection {
public:
Selection(); // Empty selection.
Selection(int start_x, int start_y, int end_x, int end_y);

const Box& GetBox() const;

Selection SaturateHorizontal(Box box);
Selection SaturateVertical(Box box);
bool IsEmpty() const { return empty_; }

void AddPart(const std::string& part, int y, int left, int right);
std::string GetParts() { return parts_.str(); }

private:
Selection(int start_x, int start_y, int end_x, int end_y, Selection* parent);

Selection* const parent_ = this;
const bool empty_ = true;
const int start_x_ = 0;
const int start_y_ = 0;
const int end_x_ = 0;
const int end_y_ = 0;
const Box box_ = {};
std::stringstream parts_;

// The position of the last inserted part.
int x_ = 0;
int y_ = 0;
};

} // namespace ftxui

#endif /* end of include guard: FTXUI_DOM_SELECTION_HPP */
17 changes: 14 additions & 3 deletions include/ftxui/screen/screen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
#ifndef FTXUI_SCREEN_SCREEN_HPP
#define FTXUI_SCREEN_SCREEN_HPP

#include <cstdint> // for uint8_t
#include <string> // for string, basic_string, allocator
#include <vector> // for vector
#include <cstdint> // for uint8_t
#include <functional> // for function
#include <string> // for string, basic_string, allocator
#include <vector> // for vector

#include "ftxui/screen/image.hpp" // for Pixel, Image
#include "ftxui/screen/terminal.hpp" // for Dimensions
#include "ftxui/util/autoreset.hpp" // for AutoReset

namespace ftxui {

Expand Down Expand Up @@ -67,9 +69,18 @@ class Screen : public Image {
uint8_t RegisterHyperlink(const std::string& link);
const std::string& Hyperlink(uint8_t id) const;

using SelectionStyle = std::function<void(Pixel&)>;
const SelectionStyle& GetSelectionStyle() const;
void SetSelectionStyle(SelectionStyle decorator);

protected:
Cursor cursor_;
std::vector<std::string> hyperlinks_ = {""};

// The current selection style. This is overridden by various dom elements.
SelectionStyle selection_style_ = [](Pixel& pixel) {
pixel.inverted ^= true;
};
};

} // namespace ftxui
Expand Down
Loading

0 comments on commit 6fafa2d

Please sign in to comment.