From 08198d162243a8bdce3af6e07be88d48b31f2736 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 11 Jun 2024 22:01:01 -0500 Subject: [PATCH] Support replacing angle brackets in SelectView popup button Allow a SelectView to be configured with custom decorators instead of the angle brackets around the current item label. Fix up calculation of the available width for the item label. --- cursive-core/src/views/select_view.rs | 49 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/cursive-core/src/views/select_view.rs b/cursive-core/src/views/select_view.rs index 5652b481..00898528 100644 --- a/cursive-core/src/views/select_view.rs +++ b/cursive-core/src/views/select_view.rs @@ -15,6 +15,8 @@ use std::cmp::{min, Ordering}; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; +use unicode_width::UnicodeWidthStr; + type SelectCallback = dyn Fn(&mut Cursive, &T) + Send + Sync; /// View to select an item among a list. @@ -72,6 +74,8 @@ pub struct SelectView { // `true` if we show a one-line view, with popup on selection. popup: bool, + // Decorators to draw around the popup button. + decorators: [String; 2], // We need the last offset to place the popup window // We "cache" it during the draw, so we need interior mutability. @@ -102,6 +106,7 @@ impl SelectView { on_submit: None, align: Align::top_left(), popup: false, + decorators: ["<".to_string(), ">".to_string()], autojump: false, last_offset: Mutex::new(Vec2::zero()), last_size: Vec2::zero(), @@ -167,6 +172,19 @@ impl SelectView { self.last_required_size = None; } + /// Use custom decorators around the popup button instead of "<" and ">". + /// + /// Chainable variant. + #[must_use] + pub fn decorators>(self, start: S, end: S) -> Self { + self.with(|s| s.set_decorators(start, end)) + } + + /// Use custom decorators around the popup button instead of "<" and ">". + pub fn set_decorators>(&mut self, start: S, end: S) { + self.decorators = [start.into(), end.into()]; + } + /// Sets a callback to be used when an item is selected. #[crate::callback_helpers] pub fn set_on_select(&mut self, cb: F) @@ -784,7 +802,13 @@ impl SelectView { // We'll want to show the popup so that the text matches. // It'll be soo cool. let item_length = self.items[focus].label.width(); - let text_offset = (self.last_size.x.saturating_sub(item_length)) / 2; + let text_offset = self + .last_size + .x + .saturating_sub(self.decorators_width()) + .saturating_sub(item_length) + / 2 + + self.decorators[0].width(); // The total offset for the window is: // * the last absolute offset at which we drew this view // * shifted to the right of the text offset @@ -829,6 +853,10 @@ impl SelectView { _ => EventResult::Ignored, } } + + fn decorators_width(&self) -> usize { + self.decorators.iter().map(|d| d.width()).sum() + } } impl SelectView { @@ -957,21 +985,22 @@ impl View for SelectView { PaletteStyle::Primary }; - let x = match printer.size.x.checked_sub(1) { - Some(x) => x, + let lw = match printer.size.x.checked_sub(self.decorators_width()) { + Some(lw) => lw, None => return, }; printer.with_style(style, |printer| { - // Prepare the entire background - printer.print_hline((1, 0), x, " "); - // Draw the borders - printer.print((0, 0), "<"); - printer.print((x, 0), ">"); + // Prepare the label background + printer.print_hline((self.decorators[0].width(), 0), lw, " "); + // Draw the decorators + printer.print((0, 0), &self.decorators[0]); + printer.print((self.decorators[0].width() + lw, 0), &self.decorators[1]); if let Some(label) = self.items.get(focus).map(|item| &item.label) { // And center the text? - let offset = HAlign::Center.get_offset(label.width(), x + 1); + let offset = + self.decorators[0].width() + HAlign::Center.get_offset(label.width(), lw); printer.print_styled((offset, 0), label); } @@ -1027,7 +1056,7 @@ impl View for SelectView { .max() .unwrap_or(1); let size = if self.popup { - Vec2::new(w + 2, 1) + Vec2::new(w + self.decorators_width(), 1) } else { let h = self.items.len();