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>
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 
17 namespace ftxui {
18 
19 namespace {
20 
21 Decorator 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({
30  padding_top,
31  hbox({
32  padding_left,
33  element,
34  }),
35  });
36  };
37 }
38 
39 class 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 {
55  NodeDecorator::Render(screen);
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 
94 Element 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 
117 class 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 {
156  if (ComponentBase::OnEvent(event)) {
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 /// ```
313  return Make<WindowImpl>(std::move(option));
314 }
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...
Definition: component.cpp:109
virtual bool OnEvent(Event)
Called in response to an event.
Definition: component.cpp:123
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.
Element window(Element title, Element content, BorderStyle border=ROUNDED)
Draw window with a title and a border around the element.
Definition: border.cpp:508
@ HEIGHT
Definition: elements.hpp:147
@ WIDTH
Definition: elements.hpp:147
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.
Definition: clear_under.cpp:38
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< ComponentBase > Component
Element emptyElement()
Definition: util.cpp:140
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:312
Element text(std::wstring text)
Display a piece of unicode text.
Definition: text.cpp:119
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:47
@ EQUAL
Definition: elements.hpp:148
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