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 
11 namespace ftxui {
12 
13 namespace {
14 
15 Decorator 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 
33 class 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,
40  Color color)
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 {
49  NodeDecorator::Render(screen);
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 
88 Element DefaultRenderState(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 
111 class 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 = render ? render(state) : DefaultRenderState(state);
140 
141  // Position and record the drawn area of the window.
142  element |= reflect(box_window_);
143  element |= PositionAndSize(left(), top(), width(), height());
144  element |= reflect(box_);
145 
146  return element;
147  }
148 
149  bool OnEvent(Event event) final {
150  if (ComponentBase::OnEvent(event)) {
151  return true;
152  }
153 
154  if (!event.is_mouse()) {
155  return false;
156  }
157 
158  mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y);
159 
160  resize_down_hover_ = false;
161  resize_top_hover_ = false;
162  resize_left_hover_ = false;
163  resize_right_hover_ = false;
164 
165  if (mouse_hover_) {
166  resize_left_hover_ = event.mouse().x == left() + box_.x_min;
167  resize_right_hover_ =
168  event.mouse().x == left() + width() - 1 + box_.x_min;
169  resize_top_hover_ = event.mouse().y == top() + box_.y_min;
170  resize_down_hover_ = event.mouse().y == top() + height() - 1 + box_.y_min;
171 
172  // Apply the component options:
173  resize_top_hover_ &= resize_top();
174  resize_left_hover_ &= resize_left();
175  resize_down_hover_ &= resize_down();
176  resize_right_hover_ &= resize_right();
177  }
178 
179  if (captured_mouse_) {
180  if (event.mouse().motion == Mouse::Released) {
181  captured_mouse_ = nullptr;
182  return true;
183  }
184 
185  if (resize_left_) {
186  width() = left() + width() - event.mouse().x + box_.x_min;
187  left() = event.mouse().x - box_.x_min;
188  }
189 
190  if (resize_right_) {
191  width() = event.mouse().x - resize_start_x - box_.x_min;
192  }
193 
194  if (resize_top_) {
195  height() = top() + height() - event.mouse().y + box_.y_min;
196  top() = event.mouse().y - box_.y_min;
197  }
198 
199  if (resize_down_) {
200  height() = event.mouse().y - resize_start_y - box_.y_min;
201  }
202 
203  if (drag_) {
204  left() = event.mouse().x - drag_start_x - box_.x_min;
205  top() = event.mouse().y - drag_start_y - box_.y_min;
206  }
207 
208  // Clamp the window size.
209  width() = std::max<int>(width(), title().size() + 2);
210  height() = std::max<int>(height(), 2);
211 
212  return true;
213  }
214 
215  resize_left_ = false;
216  resize_right_ = false;
217  resize_top_ = false;
218  resize_down_ = false;
219 
220  if (!mouse_hover_) {
221  return false;
222  }
223 
224  if (!CaptureMouse(event)) {
225  return true;
226  }
227 
228  if (event.mouse().button != Mouse::Left) {
229  return true;
230  }
231  if (event.mouse().motion != Mouse::Pressed) {
232  return true;
233  }
234 
235  TakeFocus();
236 
237  captured_mouse_ = CaptureMouse(event);
238  if (!captured_mouse_) {
239  return true;
240  }
241 
242  resize_left_ = resize_left_hover_;
243  resize_right_ = resize_right_hover_;
244  resize_top_ = resize_top_hover_;
245  resize_down_ = resize_down_hover_;
246 
247  resize_start_x = event.mouse().x - width() - box_.x_min;
248  resize_start_y = event.mouse().y - height() - box_.y_min;
249  drag_start_x = event.mouse().x - left() - box_.x_min;
250  drag_start_y = event.mouse().y - top() - box_.y_min;
251 
252  // Drag only if we are not resizeing a border yet:
253  drag_ = !resize_right_ && !resize_down_ && !resize_top_ && !resize_left_;
254  return true;
255  }
256 
257  Box box_;
258  Box box_window_;
259 
260  CapturedMouse captured_mouse_;
261  int drag_start_x = 0;
262  int drag_start_y = 0;
263  int resize_start_x = 0;
264  int resize_start_y = 0;
265 
266  bool mouse_hover_ = false;
267  bool drag_ = false;
268  bool resize_top_ = false;
269  bool resize_left_ = false;
270  bool resize_down_ = false;
271  bool resize_right_ = false;
272 
273  bool resize_top_hover_ = false;
274  bool resize_left_hover_ = false;
275  bool resize_down_hover_ = false;
276  bool resize_right_hover_ = false;
277 };
278 
279 } // namespace
280 
281 /// @brief A draggeable / resizeable window. To use multiple of them, they must
282 /// be stacked using `Container::Stacked({...})` component;
283 ///
284 /// @param option A struct holding every parameters.
285 /// @ingroup component
286 /// @see Window
287 ///
288 /// ### Example
289 ///
290 /// ```cpp
291 /// auto window_1= Window({
292 /// .inner = DummyWindowContent(),
293 /// .title = "First window",
294 /// });
295 ///
296 /// auto window_2= Window({
297 /// .inner = DummyWindowContent(),
298 /// .title = "Second window",
299 /// });
300 ///
301 /// auto container = Container::Stacked({
302 /// window_1,
303 /// window_2,
304 /// });
305 /// ```
307  return Make<WindowImpl>(std::move(option));
308 }
309 
310 }; // 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.
Element window(Element title, Element content, BorderStyle border=ROUNDED)
Draw window with a title and a border around the element.
Definition: border.cpp:507
@ 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:37
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:133
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:306
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:91
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition: vbox.cpp:83