From d75108e96058b6e00da6306a7d99c9555f645434 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <sonzogniarthur@gmail.com>
Date: Mon, 10 Feb 2025 22:51:31 +0100
Subject: [PATCH] Fix linear_gradient float precision bug.

This was reported by:
https://github.com/ArthurSonzogni/FTXUI/issues/998

Indeed, the `t` interpolation factor, which is itself interpolated might become
slightly larger than 1.0. This is due to the float precision.
This was supposedly handled, but there was an off-by-one error in the check.

Along the way, fix a bug found by a fuzzer.

Bug: https://github.com/ArthurSonzogni/FTXUI/issues/998
Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/998
---
 CHANGELOG.md                          |  3 +++
 cmake/ftxui_set_options.cmake         |  4 ----
 cmake/ftxui_test.cmake                |  1 +
 src/ftxui/component/dropdown.cpp      |  6 ++++-
 src/ftxui/component/dropdown_test.cpp | 34 +++++++++++++++++++++++++++
 src/ftxui/dom/linear_gradient.cpp     |  6 ++++-
 6 files changed, 48 insertions(+), 6 deletions(-)
 create mode 100644 src/ftxui/component/dropdown_test.cpp

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbe57ae2..1b607ece 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,9 @@ current (development)
   See #932
 - Feature: Add `SliderOption::on_change`. This allows to set a callback when the
   slider value changes. See #938.
+- Bugfix: Handle `Dropdown` with no entries.
+- Bugfix: Fix crash in `LinearGradient` due to float precision and an off-by-one
+          mistake. See #998.
 
 ### Dom
 - Feature: Add `hscroll_indicator`. It display an horizontal indicator
diff --git a/cmake/ftxui_set_options.cmake b/cmake/ftxui_set_options.cmake
index 6e5a8e00..185739e4 100644
--- a/cmake/ftxui_set_options.cmake
+++ b/cmake/ftxui_set_options.cmake
@@ -83,10 +83,6 @@ function(ftxui_set_options library)
       target_compile_options(${library} PRIVATE "-Wpedantic")
       target_compile_options(${library} PRIVATE "-Wshadow")
       target_compile_options(${library} PRIVATE "-Wunused")
-      
-      if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-        target_compile_options(${library} PRIVATE "-Wuseless-cast")
-      endif()
     endif()
   endif()
 
diff --git a/cmake/ftxui_test.cmake b/cmake/ftxui_test.cmake
index 56b21bd9..7ff1b2d7 100644
--- a/cmake/ftxui_test.cmake
+++ b/cmake/ftxui_test.cmake
@@ -13,6 +13,7 @@ add_executable(ftxui-tests
   src/ftxui/component/component_test.cpp
   src/ftxui/component/component_test.cpp
   src/ftxui/component/container_test.cpp
+  src/ftxui/component/dropdown_test.cpp
   src/ftxui/component/hoverable_test.cpp
   src/ftxui/component/input_test.cpp
   src/ftxui/component/menu_test.cpp
diff --git a/src/ftxui/component/dropdown.cpp b/src/ftxui/component/dropdown.cpp
index b268dd9f..690a6e2d 100644
--- a/src/ftxui/component/dropdown.cpp
+++ b/src/ftxui/component/dropdown.cpp
@@ -47,7 +47,11 @@ Component Dropdown(DropdownOption option) {
     Element Render() override {
       radiobox.selected =
           util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1);
-      title_ = radiobox.entries[selected_()];
+      selected_ = util::clamp(selected_(), 0, int(radiobox.entries.size()) - 1);
+
+      if (selected_() >= 0) {
+        title_ = radiobox.entries[selected_()];
+      }
 
       return transform(*open_, checkbox_->Render(), radiobox_->Render());
     }
diff --git a/src/ftxui/component/dropdown_test.cpp b/src/ftxui/component/dropdown_test.cpp
new file mode 100644
index 00000000..44434d12
--- /dev/null
+++ b/src/ftxui/component/dropdown_test.cpp
@@ -0,0 +1,34 @@
+// Copyright 2025 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
+
+#include "ftxui/component/component.hpp"  // for Horizontal, Vertical, Button, Tab
+#include "ftxui/component/component_base.hpp"  // for ComponentBase, Component
+#include "ftxui/component/event.hpp"  // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
+#include "gtest/gtest.h"  // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_FALSE, Test, EXPECT_TRUE, TEST
+
+namespace ftxui {
+
+TEST(DropdownTest, Empty) {
+  std::vector<std::string> list = {};
+  int index = 0;
+  auto dropdown = Dropdown(list, &index);
+
+  dropdown->OnEvent(Event::Return);
+
+  auto screen = Screen(8, 8);
+  auto document = dropdown->Render();
+  Render(screen, document);
+
+  EXPECT_EQ(screen.ToString(),
+            "╭──────╮\r\n"
+            "│↓     │\r\n"
+            "├──────┤\r\n"
+            "│      │\r\n"
+            "│      │\r\n"
+            "│      │\r\n"
+            "│      │\r\n"
+            "╰──────╯");
+}
+
+}  // namespace ftxui
diff --git a/src/ftxui/dom/linear_gradient.cpp b/src/ftxui/dom/linear_gradient.cpp
index 0f7d1761..5dda646d 100644
--- a/src/ftxui/dom/linear_gradient.cpp
+++ b/src/ftxui/dom/linear_gradient.cpp
@@ -97,7 +97,11 @@ Color Interpolate(const LinearGradientNormalized& gradient, float t) {
   // Find the right color in the gradient's stops.
   size_t i = 1;
   while (true) {
-    if (i > gradient.positions.size()) {
+    // Note that `t` might be slightly greater than 1.0 due to floating point
+    // precision. This is why we need to handle the case where `t` is greater
+    // than the last stop's position.
+    // See https://github.com/ArthurSonzogni/FTXUI/issues/998
+    if (i >= gradient.positions.size()) {
       const float half = 0.5F;
       return Color::Interpolate(half, gradient.colors.back(),
                                 gradient.colors.back());
-- 
GitLab