From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <c.heiss@proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by lists.proxmox.com (Postfix) with ESMTPS id A2ABC9636F
 for <pve-devel@lists.proxmox.com>; Mon, 15 Apr 2024 15:21:15 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id 82F14A931
 for <pve-devel@lists.proxmox.com>; Mon, 15 Apr 2024 15:20:45 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [94.136.29.106])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS
 for <pve-devel@lists.proxmox.com>; Mon, 15 Apr 2024 15:20:44 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 452F744A4F
 for <pve-devel@lists.proxmox.com>; Mon, 15 Apr 2024 15:20:44 +0200 (CEST)
From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Date: Mon, 15 Apr 2024 15:20:25 +0200
Message-ID: <20240415132039.1354666-2-c.heiss@proxmox.com>
X-Mailer: git-send-email 2.44.0
In-Reply-To: <20240415132039.1354666-1-c.heiss@proxmox.com>
References: <20240415132039.1354666-1-c.heiss@proxmox.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL -0.000 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 DMARC_MISSING             0.1 Missing DMARC policy
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
Subject: [pve-devel] [PATCH installer v3 1/3] tui: NumericEditView: add
 optional placeholder value
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/>
List-Post: <mailto:pve-devel@lists.proxmox.com>
List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Mon, 15 Apr 2024 13:21:15 -0000

Enables to add an optional placeholder value to `NumericEditView`, which
will be displayed in a different (darker) color and not returned by
`.get_content*()`.

Can be used for having default values in the TUI, but with different
handling in the back.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v2 -> v3:
  * when empty & focused, do not show the placeholder value at all

Changes v1 -> v2:
  * new patch

 proxmox-tui-installer/src/views/mod.rs | 66 ++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 4 deletions(-)

diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs
index 3244e76..5e5f4fb 100644
--- a/proxmox-tui-installer/src/views/mod.rs
+++ b/proxmox-tui-installer/src/views/mod.rs
@@ -2,9 +2,10 @@ use std::{net::IpAddr, rc::Rc, str::FromStr};

 use cursive::{
     event::{Event, EventResult},
+    theme::BaseColor,
     view::{Resizable, ViewWrapper},
     views::{EditView, LinearLayout, NamedView, ResizedView, SelectView, TextView},
-    Rect, Vec2, View,
+    Printer, Rect, Vec2, View,
 };

 use proxmox_installer_common::utils::CidrAddress;
@@ -24,6 +25,7 @@ pub use timezone::*;
 pub struct NumericEditView<T> {
     view: LinearLayout,
     max_value: Option<T>,
+    placeholder: Option<T>,
     max_content_width: Option<usize>,
     allow_empty: bool,
 }
@@ -36,6 +38,7 @@ impl<T: Copy + ToString + FromStr + PartialOrd> NumericEditView<T> {
         Self {
             view,
             max_value: None,
+            placeholder: None,
             max_content_width: None,
             allow_empty: false,
         }
@@ -54,6 +57,7 @@ impl<T: Copy + ToString + FromStr + PartialOrd> NumericEditView<T> {
         Self {
             view,
             max_value: None,
+            placeholder: None,
             max_content_width: None,
             allow_empty: false,
         }
@@ -84,15 +88,42 @@ impl<T: Copy + ToString + FromStr + PartialOrd> NumericEditView<T> {
         self
     }

+    /// Sets a placeholder value for this view. Implies `allow_empty(true)`.
+    /// Implies `allow_empty(true)`.
+    ///
+    /// # Arguments
+    /// `placeholder` - The placeholder value to set for this view.
+    #[allow(unused)]
+    pub fn placeholder(mut self, placeholder: T) -> Self {
+        self.placeholder = Some(placeholder);
+        self.allow_empty(true)
+    }
+
+    /// Returns the current value of the view. If a placeholder is defined and
+    /// no value is currently set, the placeholder value is returned.
+    ///
+    /// **This should only be called when `allow_empty = false` or a placeholder
+    /// is set.**
     pub fn get_content(&self) -> Result<T, <T as FromStr>::Err> {
-        assert!(!self.allow_empty);
-        self.inner().get_content().parse()
+        let content = self.inner().get_content();
+
+        if content.is_empty() {
+            if let Some(placeholder) = self.placeholder {
+                return Ok(placeholder);
+            }
+        }
+
+        assert!(!(self.allow_empty && self.placeholder.is_none()));
+        content.parse()
     }

+    /// Returns the current value of the view, or [`None`] if the view is
+    /// currently empty.
     pub fn get_content_maybe(&self) -> Option<Result<T, <T as FromStr>::Err>> {
         let content = self.inner().get_content();
+
         if !content.is_empty() {
-            Some(self.inner().get_content().parse())
+            Some(content.parse())
         } else {
             None
         }
@@ -157,6 +188,25 @@ impl<T: Copy + ToString + FromStr + PartialOrd> NumericEditView<T> {
         std::mem::swap(self.inner_mut(), &mut inner);
         self
     }
+
+    /// Generic `wrap_draw()` implementation for [`ViewWrapper`].
+    ///
+    /// # Arguments
+    /// * `printer` - The [`Printer`] to draw to the base view.
+    fn wrap_draw_inner(&self, printer: &Printer) {
+        self.view.draw(printer);
+
+        if self.inner().get_content().is_empty() && !printer.focused {
+            if let Some(placeholder) = self.placeholder {
+                let placeholder = placeholder.to_string();
+
+                printer.with_color(
+                    (BaseColor::Blue.light(), BaseColor::Blue.dark()).into(),
+                    |printer| printer.print((0, 0), &placeholder),
+                );
+            }
+        }
+    }
 }

 pub type FloatEditView = NumericEditView<f64>;
@@ -165,6 +215,10 @@ pub type IntegerEditView = NumericEditView<usize>;
 impl ViewWrapper for FloatEditView {
     cursive::wrap_impl!(self.view: LinearLayout);

+    fn wrap_draw(&self, printer: &Printer) {
+        self.wrap_draw_inner(printer);
+    }
+
     fn wrap_on_event(&mut self, event: Event) -> EventResult {
         let original = self.inner_mut().get_content();

@@ -204,6 +258,10 @@ impl FloatEditView {
 impl ViewWrapper for IntegerEditView {
     cursive::wrap_impl!(self.view: LinearLayout);

+    fn wrap_draw(&self, printer: &Printer) {
+        self.wrap_draw_inner(printer);
+    }
+
     fn wrap_on_event(&mut self, event: Event) -> EventResult {
         let original = self.inner_mut().get_content();

--
2.44.0