From 6fe831032168358f569b9ddcf243602bfbdf32e6 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <sonzogniarthur@gmail.com>
Date: Sun, 22 Jan 2023 11:02:27 +0100
Subject: [PATCH] Feature: `strikethrough` and `underlinedDouble` decorator.
 (#561)

This resolves:
https://github.com/ArthurSonzogni/FTXUI/issues/560
---
 CHANGELOG.md                             |  3 ++
 CMakeLists.txt                           |  2 ++
 README.md                                |  2 ++
 doc/mainpage.md                          |  4 ++-
 examples/dom/CMakeLists.txt              |  2 ++
 examples/dom/style_gallery.cpp           | 18 ++++++------
 examples/dom/style_strikethrough.cpp     | 25 ++++++++++++++++
 examples/dom/style_underlined_double.cpp | 25 ++++++++++++++++
 include/ftxui/dom/elements.hpp           |  2 ++
 include/ftxui/screen/screen.hpp          |  4 +++
 src/ftxui/dom/strikethrough.cpp          | 36 ++++++++++++++++++++++++
 src/ftxui/dom/underlined_double.cpp      | 36 ++++++++++++++++++++++++
 src/ftxui/screen/screen.cpp              | 26 ++++++++++++++---
 13 files changed, 172 insertions(+), 13 deletions(-)
 create mode 100644 examples/dom/style_strikethrough.cpp
 create mode 100644 examples/dom/style_underlined_double.cpp
 create mode 100644 src/ftxui/dom/strikethrough.cpp
 create mode 100644 src/ftxui/dom/underlined_double.cpp

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6aa08809..35ae72ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ current (development)
 ---------------------
 
 ### DOM
+- Feature: more styles:
+  - `strikethrough`
+  - `underlinedDouble`
 - Feature: Customize the cursor. Add the following decorators:
   - `focusCursorBlock`
   - `focusCursorBlockBlinking`
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dab2557c..adb4ec58 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -79,9 +79,11 @@ add_library(dom
   src/ftxui/dom/separator.cpp
   src/ftxui/dom/size.cpp
   src/ftxui/dom/spinner.cpp
+  src/ftxui/dom/strikethrough.cpp
   src/ftxui/dom/table.cpp
   src/ftxui/dom/text.cpp
   src/ftxui/dom/underlined.cpp
+  src/ftxui/dom/underlined_double.cpp
   src/ftxui/dom/util.cpp
   src/ftxui/dom/vbox.cpp
 )
diff --git a/README.md b/README.md
index c9aa65cb..7f1b0670 100644
--- a/README.md
+++ b/README.md
@@ -122,7 +122,9 @@ An element can be decorated using the functions:
   - `dim`
   - `inverted`
   - `underlined`
+  - `underlinedDouble`
   - `blink`
+  - `strikethrough`
   - `color`
   - `bgcolor`
 
diff --git a/doc/mainpage.md b/doc/mainpage.md
index 2395748f..9c036602 100644
--- a/doc/mainpage.md
+++ b/doc/mainpage.md
@@ -453,6 +453,8 @@ Element bold(Element);
 Element dim(Element);
 Element inverted(Element);
 Element underlined(Element);
+Element underlinedDouble(Element);
+Element strikethrough(Element);
 Element blink(Element);
 Decorator color(Color);
 Decorator bgcolor(Color);
@@ -469,7 +471,7 @@ underlined(bold(text("This text is bold and underlined")))
 
 Alternatively, use the pipe operator to chain it on your element:
 ```cpp
-text("This text is bold")) | bold | underlined
+text("This text is bold") | bold | underlined
 ```
 
 ## Layout {#dom-layout}
diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt
index ee2265ce..df6e1b9c 100644
--- a/examples/dom/CMakeLists.txt
+++ b/examples/dom/CMakeLists.txt
@@ -26,7 +26,9 @@ example(style_color)
 example(style_dim)
 example(style_gallery)
 example(style_inverted)
+example(style_strikethrough)
 example(style_underlined)
+example(style_underlined_double)
 example(table)
 example(vbox_hbox)
 example(vflow)
diff --git a/examples/dom/style_gallery.cpp b/examples/dom/style_gallery.cpp
index 9303e67e..88263888 100644
--- a/examples/dom/style_gallery.cpp
+++ b/examples/dom/style_gallery.cpp
@@ -10,14 +10,16 @@ int main(int argc, const char* argv[]) {
   // clang-format off
   auto document =
     hbox({
-      text("normal")                           , text(" ") ,
-      text("bold")      | bold                 , text(" ") ,
-      text("dim")       | dim                  , text(" ") ,
-      text("inverted")  | inverted             , text(" ") ,
-      text("underlined")| underlined           , text(" ") ,
-      text("blink")     | blink                , text(" ") ,
-      text("color")     | color(Color::Blue)   , text(" ") ,
-      text("bgcolor")   | bgcolor(Color::Blue),
+      text("normal")                                    , text(" ") ,
+      text("bold")               | bold                 , text(" ") ,
+      text("dim")                | dim                  , text(" ") ,
+      text("inverted")           | inverted             , text(" ") ,
+      text("underlined")         | underlined           , text(" ") ,
+      text("underlinedDouble")   | underlinedDouble     , text(" ") ,
+      text("blink")              | blink                , text(" ") ,
+      text("strikethrough")      | strikethrough        , text(" ") ,
+      text("color")              | color(Color::Blue)   , text(" ") ,
+      text("bgcolor")            | bgcolor(Color::Blue) ,
     });
   // clang-format on
   auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
diff --git a/examples/dom/style_strikethrough.cpp b/examples/dom/style_strikethrough.cpp
new file mode 100644
index 00000000..c2c8355f
--- /dev/null
+++ b/examples/dom/style_strikethrough.cpp
@@ -0,0 +1,25 @@
+#include <ftxui/dom/elements.hpp>  // for text, operator|, strikethrough, Fit, hbox, Element
+#include <ftxui/screen/screen.hpp>  // for Full, Screen
+#include <memory>                   // for allocator
+
+#include "ftxui/dom/node.hpp"      // for Render
+#include "ftxui/screen/color.hpp"  // for ftxui
+
+int main(int argc, const char* argv[]) {
+  using namespace ftxui;
+  auto document =  //
+      hbox({
+          text("This text is "),
+          text("strikethrough") | strikethrough,
+          text(". Do you like it?"),
+      });
+  auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
+  Render(screen, document);
+  screen.Print();
+
+  return 0;
+}
+
+// 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.
diff --git a/examples/dom/style_underlined_double.cpp b/examples/dom/style_underlined_double.cpp
new file mode 100644
index 00000000..ea2a9984
--- /dev/null
+++ b/examples/dom/style_underlined_double.cpp
@@ -0,0 +1,25 @@
+#include <ftxui/dom/elements.hpp>  // for text, operator|, underlinedDouble, Fit, hbox, Element
+#include <ftxui/screen/screen.hpp>  // for Full, Screen
+#include <memory>                   // for allocator
+
+#include "ftxui/dom/node.hpp"      // for Render
+#include "ftxui/screen/color.hpp"  // for ftxui
+
+int main(int argc, const char* argv[]) {
+  using namespace ftxui;
+  auto document =  //
+      hbox({
+          text("This text is "),
+          text("underlinedDouble") | underlinedDouble,
+          text(". Do you like it?"),
+      });
+  auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
+  Render(screen, document);
+  screen.Print();
+
+  return 0;
+}
+
+// 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.
diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp
index ce148016..b3935c50 100644
--- a/include/ftxui/dom/elements.hpp
+++ b/include/ftxui/dom/elements.hpp
@@ -83,7 +83,9 @@ Element bold(Element);
 Element dim(Element);
 Element inverted(Element);
 Element underlined(Element);
+Element underlinedDouble(Element);
 Element blink(Element);
+Element strikethrough(Element);
 Decorator color(Color);
 Decorator bgcolor(Color);
 Element color(Color, Element);
diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp
index 96242a36..d8a5329d 100644
--- a/include/ftxui/screen/screen.hpp
+++ b/include/ftxui/screen/screen.hpp
@@ -30,6 +30,8 @@ struct Pixel {
   bool dim : 1;
   bool inverted : 1;
   bool underlined : 1;
+  bool underlined_double : 1;
+  bool strikethrough : 1;
   bool automerge : 1;
 
   Pixel()
@@ -38,6 +40,8 @@ struct Pixel {
         dim(false),
         inverted(false),
         underlined(false),
+        underlined_double(false),
+        strikethrough(false),
         automerge(false) {}
 };
 
diff --git a/src/ftxui/dom/strikethrough.cpp b/src/ftxui/dom/strikethrough.cpp
new file mode 100644
index 00000000..13c8559e
--- /dev/null
+++ b/src/ftxui/dom/strikethrough.cpp
@@ -0,0 +1,36 @@
+#include <memory>   // for make_shared
+#include <utility>  // for move
+
+#include "ftxui/dom/elements.hpp"        // for Element, strikethrough
+#include "ftxui/dom/node.hpp"            // for Node
+#include "ftxui/dom/node_decorator.hpp"  // for NodeDecorator
+#include "ftxui/screen/box.hpp"          // for Box
+#include "ftxui/screen/screen.hpp"       // for Pixel, Screen
+
+namespace ftxui {
+
+/// @brief Apply a strikethrough to text.
+/// @ingroup dom
+Element strikethrough(Element child) {
+  class Impl : public NodeDecorator {
+   public:
+    using NodeDecorator::NodeDecorator;
+
+    void Render(Screen& screen) override {
+      for (int y = box_.y_min; y <= box_.y_max; ++y) {
+        for (int x = box_.x_min; x <= box_.x_max; ++x) {
+          screen.PixelAt(x, y).strikethrough = true;
+        }
+      }
+      Node::Render(screen);
+    }
+  };
+
+  return std::make_shared<Impl>(std::move(child));
+}
+
+}  // namespace ftxui
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/src/ftxui/dom/underlined_double.cpp b/src/ftxui/dom/underlined_double.cpp
new file mode 100644
index 00000000..c1089f7b
--- /dev/null
+++ b/src/ftxui/dom/underlined_double.cpp
@@ -0,0 +1,36 @@
+#include <memory>   // for make_shared
+#include <utility>  // for move
+
+#include "ftxui/dom/elements.hpp"        // for Element, underlinedDouble
+#include "ftxui/dom/node.hpp"            // for Node
+#include "ftxui/dom/node_decorator.hpp"  // for NodeDecorator
+#include "ftxui/screen/box.hpp"          // for Box
+#include "ftxui/screen/screen.hpp"       // for Pixel, Screen
+
+namespace ftxui {
+
+/// @brief Apply a underlinedDouble to text.
+/// @ingroup dom
+Element underlinedDouble(Element child) {
+  class Impl : public NodeDecorator {
+   public:
+    using NodeDecorator::NodeDecorator;
+
+    void Render(Screen& screen) override {
+      for (int y = box_.y_min; y <= box_.y_max; ++y) {
+        for (int x = box_.x_min; x <= box_.x_max; ++x) {
+          screen.PixelAt(x, y).underlined_double = true;
+        }
+      }
+      Node::Render(screen);
+    }
+  };
+
+  return std::make_shared<Impl>(std::move(child));
+}
+
+}  // namespace ftxui
+
+// Copyright 2023 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp
index 96cef9f2..21a93cab 100644
--- a/src/ftxui/screen/screen.cpp
+++ b/src/ftxui/screen/screen.cpp
@@ -66,6 +66,16 @@ void UpdatePixelStyle(std::stringstream& ss,
     previous.dim = false;
   }
 
+  if ((!next.underlined && previous.underlined) ||
+      (!next.underlined_double && previous.underlined_double)) {
+    // We might have wrongfully reset underlined or underlinedbold because they
+    // share the same resetter. Take it into account so that the side effect
+    // will cause it to be set again below.
+    ss << "\x1B[24m";  // UNDERLINED_RESET
+    previous.underlined = false;
+    previous.underlined_double = false;
+  }
+
   if (next.bold && !previous.bold) {
     ss << "\x1B[1m";  // BOLD_SET
   }
@@ -78,10 +88,6 @@ void UpdatePixelStyle(std::stringstream& ss,
     ss << "\x1B[4m";  // UNDERLINED_SET
   }
 
-  if (!next.underlined && previous.underlined) {
-    ss << "\x1B[24m";  // UNDERLINED_RESET
-  }
-
   if (next.blink && !previous.blink) {
     ss << "\x1B[5m";  // BLINK_SET
   }
@@ -98,6 +104,18 @@ void UpdatePixelStyle(std::stringstream& ss,
     ss << "\x1B[27m";  // INVERTED_RESET
   }
 
+  if (next.strikethrough && !previous.strikethrough) {
+    ss << "\x1B[9m";  // CROSSED_OUT
+  }
+
+  if (!next.strikethrough && previous.strikethrough) {
+    ss << "\x1B[29m";  // CROSSED_OUT_RESET
+  }
+
+  if (next.underlined_double && !previous.underlined_double) {
+    ss << "\x1B[21m";  // DOUBLE_UNDERLINED_SET
+  }
+
   if (next.foreground_color != previous.foreground_color ||
       next.background_color != previous.background_color) {
     ss << "\x1B[" + next.foreground_color.Print(false) + "m";
-- 
GitLab