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