FTXUI  5.0.0
C++ functional terminal UI.
window.cpp
Go to the documentation of this file.
1// Copyright 2023 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4#define NOMINMAX
5#include <algorithm>
8#include <ftxui/component/screen_interactive.hpp> // for ScreenInteractive
9#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
10
11namespace ftxui {
12
13namespace {
14
15Decorator PositionAndSize(int left, int top, int width, int height) {
16 return [=](Element element) {
17 element |= size(WIDTH, EQUAL, width);
18 element |= size(HEIGHT, EQUAL, height);
19
20 auto padding_left = emptyElement() | size(WIDTH, EQUAL, left);
21 auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top);
22
23 return vbox({
24 padding_top,
25 hbox({
26 padding_left,
27 element,
28 }),
29 });
30 };
31}
32
33class ResizeDecorator : public NodeDecorator {
34 public:
35 ResizeDecorator(Element child,
36 bool resize_left,
37 bool resize_right,
38 bool resize_top,
39 bool resize_down,
41 : NodeDecorator(std::move(child)),
42 color_(color),
43 resize_left_(resize_left),
44 resize_right_(resize_right),
45 resize_top_(resize_top),
46 resize_down_(resize_down) {}
47
48 void Render(Screen& screen) override {
50
51 if (resize_left_) {
52 for (int y = box_.y_min; y <= box_.y_max; ++y) {
53 auto& cell = screen.PixelAt(box_.x_min, y);
54 cell.foreground_color = color_;
55 cell.automerge = false;
56 }
57 }
58 if (resize_right_) {
59 for (int y = box_.y_min; y <= box_.y_max; ++y) {
60 auto& cell = screen.PixelAt(box_.x_max, y);
61 cell.foreground_color = color_;
62 cell.automerge = false;
63 }
64 }
65 if (resize_top_) {
66 for (int x = box_.x_min; x <= box_.x_max; ++x) {
67 auto& cell = screen.PixelAt(x, box_.y_min);
68 cell.foreground_color = color_;
69 cell.automerge = false;
70 }
71 }
72 if (resize_down_) {
73 for (int x = box_.x_min; x <= box_.x_max; ++x) {
74 auto& cell = screen.PixelAt(x, box_.y_max);
75 cell.foreground_color = color_;
76 cell.automerge = false;
77 }
78 }
79 }
80
81 Color color_;
82 const bool resize_left_;
83 const bool resize_right_;
84 const bool resize_top_;
85 const bool resize_down_;
86};
87
88Element DefaultRenderState(int i, const WindowRenderState& state) {
89 Element element = state.inner;
90 if (state.active) {
91 element |= dim;
92 }
93
94 element = window(text(state.title), element);
95 element |= clear_under;
96
97 const Color color = Color::Red;
98
99 element = std::make_shared<ResizeDecorator>( //
100 element, //
101 state.hover_left, //
102 state.hover_right, //
103 state.hover_top, //
104 state.hover_down, //
105 color //
106 );
107
108 return element;
109}
110
111class WindowImpl : public ComponentBase, public WindowOptions {
112 public:
113 explicit WindowImpl(WindowOptions option) : WindowOptions(std::move(option)) {
114 if (!inner) {
115 inner = Make<ComponentBase>();
116 }
117 Add(inner);
118 }
119
120 private:
121 Element Render() final {
122 auto element = ComponentBase::Render();
123
124 const bool captureable =
125 captured_mouse_ || ScreenInteractive::Active()->CaptureMouse();
126
127 const WindowRenderState state = {
128 element,
129 title(),
130 Active(),
131 drag_,
132 resize_left_ || resize_right_ || resize_down_ || resize_top_,
133 (resize_left_hover_ || resize_left_) && captureable,
134 (resize_right_hover_ || resize_right_) && captureable,
135 (resize_top_hover_ || resize_top_) && captureable,
136 (resize_down_hover_ || resize_down_) && captureable,
137 };
138
139 element =
140 render ? render(state)
141 : DefaultRenderState(reinterpret_cast<uintptr_t>(this), state);
142
143 // Position and record the drawn area of the window.
144 element |= reflect(box_window_);
145 element |= PositionAndSize(left(), top(), width(), height());
146 element |= reflect(box_);
147
148 return element;
149 }
150
151 bool OnEvent(Event event) final {
152 if (ComponentBase::OnEvent(event)) {
153 return true;
154 }
155
156 if (!event.is_mouse()) {
157 return false;
158 }
159
160 mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y);
161
162 resize_down_hover_ = false;
163 resize_top_hover_ = false;
164 resize_left_hover_ = false;
165 resize_right_hover_ = false;
166
167 if (mouse_hover_) {
168 resize_left_hover_ = event.mouse().x == left() + box_.x_min;
169 resize_right_hover_ =
170 event.mouse().x == left() + width() - 1 + box_.x_min;
171 resize_top_hover_ = event.mouse().y == top() + box_.y_min;
172 resize_down_hover_ = event.mouse().y == top() + height() - 1 + box_.y_min;
173
174 // Apply the component options:
175 resize_top_hover_ &= resize_top();
176 resize_left_hover_ &= resize_left();
177 resize_down_hover_ &= resize_down();
178 resize_right_hover_ &= resize_right();
179 }
180
181 if (captured_mouse_) {
182 if (event.mouse().motion == Mouse::Released) {
183 captured_mouse_ = nullptr;
184 return true;
185 }
186
187 if (resize_left_) {
188 width() = left() + width() - event.mouse().x + box_.x_min;
189 left() = event.mouse().x - box_.x_min;
190 }
191
192 if (resize_right_) {
193 width() = event.mouse().x - resize_start_x - box_.x_min;
194 }
195
196 if (resize_top_) {
197 height() = top() + height() - event.mouse().y + box_.y_min;
198 top() = event.mouse().y - box_.y_min;
199 }
200
201 if (resize_down_) {
202 height() = event.mouse().y - resize_start_y - box_.y_min;
203 }
204
205 if (drag_) {
206 left() = event.mouse().x - drag_start_x - box_.x_min;
207 top() = event.mouse().y - drag_start_y - box_.y_min;
208 }
209
210 // Clamp the window size.
211 width() = std::max<int>(width(), title().size() + 2);
212 height() = std::max<int>(height(), 2);
213
214 return true;
215 }
216
217 resize_left_ = false;
218 resize_right_ = false;
219 resize_top_ = false;
220 resize_down_ = false;
221
222 if (!mouse_hover_) {
223 return false;
224 }
225
226 if (!CaptureMouse(event)) {
227 return true;
228 }
229
230 if (!event.mouse().IsPressed()) {
231 return true;
232 }
233
234 TakeFocus();
235
236 captured_mouse_ = CaptureMouse(event);
237 if (!captured_mouse_) {
238 return true;
239 }
240
241 resize_left_ = resize_left_hover_;
242 resize_right_ = resize_right_hover_;
243 resize_top_ = resize_top_hover_;
244 resize_down_ = resize_down_hover_;
245
246 resize_start_x = event.mouse().x - width() - box_.x_min;
247 resize_start_y = event.mouse().y - height() - box_.y_min;
248 drag_start_x = event.mouse().x - left() - box_.x_min;
249 drag_start_y = event.mouse().y - top() - box_.y_min;
250
251 // Drag only if we are not resizeing a border yet:
252 drag_ = !resize_right_ && !resize_down_ && !resize_top_ && !resize_left_;
253 return true;
254 }
255
256 Box box_;
257 Box box_window_;
258
259 CapturedMouse captured_mouse_;
260 int drag_start_x = 0;
261 int drag_start_y = 0;
262 int resize_start_x = 0;
263 int resize_start_y = 0;
264
265 bool mouse_hover_ = false;
266 bool drag_ = false;
267 bool resize_top_ = false;
268 bool resize_left_ = false;
269 bool resize_down_ = false;
270 bool resize_right_ = false;
271
272 bool resize_top_hover_ = false;
273 bool resize_left_hover_ = false;
274 bool resize_down_hover_ = false;
275 bool resize_right_hover_ = false;
276};
277
278} // namespace
279
280/// @brief A draggeable / resizeable window. To use multiple of them, they must
281/// be stacked using `Container::Stacked({...})` component;
282///
283/// @param option A struct holding every parameters.
284/// @ingroup component
285/// @see Window
286///
287/// ### Example
288///
289/// ```cpp
290/// auto window_1= Window({
291/// .inner = DummyWindowContent(),
292/// .title = "First window",
293/// });
294///
295/// auto window_2= Window({
296/// .inner = DummyWindowContent(),
297/// .title = "Second window",
298/// });
299///
300/// auto container = Container::Stacked({
301/// window_1,
302/// window_2,
303/// });
304/// ```
306 return Make<WindowImpl>(std::move(option));
307}
308
309}; // namespace ftxui
virtual Element Render()
Draw the component. Build a ftxui::Element to be drawn on the ftxi::Screen representing this ftxui::C...
Definition: component.cpp:92
virtual bool OnEvent(Event)
Called in response to an event.
Definition: component.cpp:106
virtual void Render(Screen &screen)
Display an element on a ftxui::Screen.
Definition: node.cpp:32
static ScreenInteractive * Active()
Return the currently active screen, or null if none.
CapturedMouse CaptureMouse()
Try to get the unique lock about behing able to capture the mouse.
@ HEIGHT
Definition: elements.hpp:148
@ WIDTH
Definition: elements.hpp:148
std::function< Element(Element)> Decorator
Definition: elements.hpp:25
Element clear_under(Element element)
Before drawing |child|, clear the pixels below. This is useful in.
Definition: clear_under.cpp:37
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Definition: size.cpp:90
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::shared_ptr< Node > Element
Definition: elements.hpp:23
std::shared_ptr< ComponentBase > Component
Element emptyElement()
Definition: util.cpp:135
Element window(Element title, Element content)
Draw window with a title and a border around the element.
Definition: border.cpp:500
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition: hbox.cpp:83
Component Window(WindowOptions option)
A draggeable / resizeable window. To use multiple of them, they must be stacked using Container::Stac...
Definition: window.cpp:305
Element text(std::wstring text)
Display a piece of unicode text.
Definition: text.cpp:120
Decorator reflect(Box &box)
Definition: reflect.cpp:44
Element dim(Element)
Use a light font, for elements with less emphasis.
Definition: dim.cpp:33
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition: node.cpp:47
@ EQUAL
Definition: elements.hpp:149
Decorator color(Color)
Decorate using a foreground color.
Definition: color.cpp:110
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition: vbox.cpp:83