Skip to content

Commit

Permalink
Feature #781 menubar to render styledstrings (#784)
Browse files Browse the repository at this point in the history
* bump enumset verstion to 1.1.0

* add menubar's ability to render styledstrings

* create example how to use

* add record to the examples/Readme.md

* add tests

* small refactoring

---------

Co-authored-by: Alexandre Bury <alexandre.bury@gmail.com>
  • Loading branch information
svfat and gyscos authored May 30, 2024
1 parent 3d7d6b7 commit 03451ae
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 12 deletions.
2 changes: 1 addition & 1 deletion cursive-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repository = "gyscos/cursive"

[dependencies]
enum-map = "2.0"
enumset = "1.0.4"
enumset = "1.1.0"
log = "0.4"
unicode-segmentation = "1"
unicode-width = "0.1"
Expand Down
83 changes: 82 additions & 1 deletion cursive-core/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@
//! [`Tree`]: struct.Tree.html
//! [menubar]: ../struct.Cursive.html#method.menubar

use crate::{event::Callback, utils::markup::StyledString, Cursive, With};
use crate::utils::span::{IndexedCow, IndexedSpan, SpannedStr};
use crate::{
event::Callback, theme::ColorStyle, theme::ColorType, theme::Style,
utils::markup::StyledString, Cursive, With,
};
use enumset::EnumSet;
use std::sync::Arc;

const PLAIN_1CHAR_SPAN: &'static [IndexedSpan<Style>] = &[IndexedSpan {
content: IndexedCow::Borrowed { start: 0, end: 1 },
attr: Style {
effects: EnumSet::EMPTY, // This needs a recent enough `enumset` dependency, we should bump the minimum version to 1.1.0
color: ColorStyle {
front: ColorType::InheritParent,
back: ColorType::InheritParent,
},
},
width: 1,
}];

/// Root of a menu tree.
#[derive(Default, Clone)]
pub struct Tree {
Expand Down Expand Up @@ -92,6 +109,18 @@ impl Item {
}
}

/// Returns the styled lable for this item
///
/// Returns a vertical bar string if `self` is a delimiter.
pub fn styled_label(&self) -> SpannedStr<Style> {
match *self {
Item::Delimiter => SpannedStr::new("|", PLAIN_1CHAR_SPAN),
Item::Leaf { ref label, .. } | Item::Subtree { ref label, .. } => {
SpannedStr::from(label)
}
}
}

/// Returns true if this item is enabled.
///
/// Only labels and subtrees can be enabled. Delimiters
Expand Down Expand Up @@ -325,3 +354,55 @@ impl Tree {
self.children.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::{ColorStyle, ColorType, Style};
use crate::utils::span::Span;
use enumset::EnumSet;

#[test]
fn test_styled_label_delimiter() {
let item = Item::Delimiter;
let styled_label = item.styled_label();
assert_eq!(styled_label.source(), "|");

let expected_spans: Vec<Span<Style>> = vec![Span {
content: "|",
attr: &Style {
effects: EnumSet::EMPTY,
color: ColorStyle {
front: ColorType::InheritParent,
back: ColorType::InheritParent,
},
},
width: 1,
}];

assert_eq!(styled_label.spans().collect::<Vec<_>>(), expected_spans);
}

#[test]
fn test_styled_label_leaf() {
let label = StyledString::plain("Leaf");
let item = Item::Leaf {
label: label.clone(),
enabled: true,
cb: Callback::from_fn(|_| {}),
};
let styled_label = item.styled_label();
assert_eq!(styled_label.source(), "Leaf");
}

#[test]
fn test_styled_label_subtree() {
let label = StyledString::plain("Subtree");
let item = Item::Subtree {
label: label.clone(),
tree: Tree::default().into(),
enabled: true,
};
let styled_label = item.styled_label();
assert_eq!(styled_label.source(), "Subtree");
}
}
31 changes: 31 additions & 0 deletions cursive-core/src/utils/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ where
pub fn is_empty(&self) -> bool {
self.source.is_empty() || self.spans.is_empty()
}

/// Returns the width taken by this string.
///
/// This is the sum of the width of each span.
pub fn width(&self) -> usize {
self.spans().map(|s| s.width).sum()
}
}

impl<'a, T> Clone for SpannedStr<'a, T> {
Expand Down Expand Up @@ -604,3 +611,27 @@ impl IndexedCow {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::theme::Style;

#[test]
fn test_spanned_str_width() {
let spans = vec![
IndexedSpan {
content: IndexedCow::Borrowed { start: 0, end: 5 },
attr: Style::default(),
width: 5,
},
IndexedSpan {
content: IndexedCow::Borrowed { start: 6, end: 11 },
attr: Style::default(),
width: 5,
},
];
let spanned_str = SpannedStr::new("Hello World", &spans);
assert_eq!(spanned_str.width(), 10);
}
}
23 changes: 13 additions & 10 deletions cursive-core/src/views/menubar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ fn show_child(s: &mut Cursive, offset: Vec2, menu: Arc<menu::Tree>) {
);
}


impl View for Menubar {
fn draw(&self, printer: &Printer) {
// Draw the bar at the top
Expand All @@ -276,8 +277,8 @@ impl View for Menubar {
// TODO: draw the rest
let mut offset = 1;
for (i, item) in self.root.children.iter().enumerate() {
let label = item.label();

let label = item.styled_label();
let label_width = label.width();
// We print disabled items differently, except delimiters,
// which are still white.
let enabled = printer.enabled && (item.is_enabled() || item.is_delimiter());
Expand All @@ -286,18 +287,20 @@ impl View for Menubar {
// because it's ugly on the menubar.
let selected = (self.state != State::Inactive) && (i == self.focus);

let style = if !enabled {
PaletteStyle::Secondary
} else if selected {
PaletteStyle::Highlight
} else {
PaletteStyle::Primary
let style = match (enabled, selected) {
(false, _) => PaletteStyle::Secondary,
(true, true) => PaletteStyle::Highlight,
_ => PaletteStyle::Primary,
};

printer.with_style(style, |printer| {
printer.print((offset, 0), &format!(" {label} "));
printer.print((offset, 0), " ");
offset += 1;
printer.print_styled((offset, 0), label);
offset += label_width;
printer.print((offset, 0), " ");
offset += 1;
});
offset += label.width() + 2;
}
});
}
Expand Down
4 changes: 4 additions & 0 deletions cursive/examples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ This example uses a `LinearView` to put multiple views side-by-side.
Here we learn how to create a menubar at the top of the screen, and populate
it with static and dynamic entried.

## [`menubar_styles`](./menubar_styles.rs)

Example of using StyledString in menubar lables.

## [`logs`](./logs.rs)

This example defines a custom view to display asynchronous input from a
Expand Down
55 changes: 55 additions & 0 deletions cursive/examples/menubar_styles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use cursive::theme::{BaseColor, Color, Effect, Style};
use cursive::utils::markup::StyledString;
use cursive::{menu, traits::*};

fn main() {
let mut siv = cursive::default();

let mut styles_label = StyledString::plain("");
styles_label.append(StyledString::styled("S", Color::Dark(BaseColor::Red)));
styles_label.append(StyledString::styled("t", Color::Dark(BaseColor::Green)));
styles_label.append(StyledString::styled("y", Color::Dark(BaseColor::Yellow)));
styles_label.append(StyledString::styled("l", Color::Dark(BaseColor::Blue)));
styles_label.append(StyledString::styled("e", Color::Dark(BaseColor::Magenta)));
styles_label.append(StyledString::styled("s", Color::Dark(BaseColor::Cyan)));

let quit_label = StyledString::styled(
"Quit",
Style::from(Color::Dark(BaseColor::Red)).combine(Effect::Bold),
);

let sub_item_labels = vec![
StyledString::styled("Black", Color::Dark(BaseColor::Black)),
StyledString::styled("Red", Color::Dark(BaseColor::Red)),
StyledString::styled("Green", Color::Dark(BaseColor::Green)),
StyledString::styled("Yellow", Color::Dark(BaseColor::Yellow)),
StyledString::styled("Blue", Color::Dark(BaseColor::Blue)),
StyledString::styled("Magenta", Color::Dark(BaseColor::Magenta)),
StyledString::styled("Cyan", Color::Dark(BaseColor::Cyan)),
StyledString::styled("White", Color::Dark(BaseColor::White)),
StyledString::styled("Light Black", Color::Light(BaseColor::Black)),
StyledString::styled("Light Red", Color::Light(BaseColor::Red)),
StyledString::styled("Light Green", Color::Light(BaseColor::Green)),
StyledString::styled("Light Yellow", Color::Light(BaseColor::Yellow)),
StyledString::styled("Light Blue", Color::Light(BaseColor::Blue)),
StyledString::styled("Light Magenta", Color::Light(BaseColor::Magenta)),
StyledString::styled("Light Cyan", Color::Light(BaseColor::Cyan)),
StyledString::styled("Light White", Color::Light(BaseColor::White)),
];

siv.menubar()
.add_subtree(
styles_label,
menu::Tree::new().with(|tree| {
for label in &sub_item_labels {
tree.add_leaf(label.clone(), |_| {});
}
}),
)
.add_delimiter()
.add_leaf(quit_label, |s| s.quit());

siv.set_autohide_menu(false);

siv.run();
}

0 comments on commit 03451ae

Please sign in to comment.