Skip to content

Commit

Permalink
Add EditableText family of palette styles
Browse files Browse the repository at this point in the history
  • Loading branch information
gyscos committed Oct 4, 2023
1 parent bfc6eab commit e14706e
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 114 deletions.
39 changes: 38 additions & 1 deletion cursive-core/src/theme/palette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ fn default_styles() -> EnumMap<PaletteStyle, Style> {
color: ColorStyle::highlight_inactive().invert(),
effects: enumset::enum_set!(Effect::Reverse),
},
EditableText => Style {
color: ColorStyle::secondary(),
effects: enumset::enum_set!(Effect::Reverse),
},
EditableTextCursor => ColorStyle::secondary().into(),
EditableTextInactive => ColorStyle::secondary().into(),
}
}

Expand Down Expand Up @@ -360,6 +366,24 @@ pub enum PaletteColor {
}

/// Style entry in a palette.
///
/// This represents a color "role". The palette will resolve this to a `Style`.
///
/// For example, `PaletteStyle::Highlight` should be used when drawing highlighted text.
/// In the default palette, it will resolve to a `Style` made of:
/// * The `Reverse` effect (front and background will be swapped).
/// * A front color of `PaletteColor::Highlight` (but with the reverse effect,
/// it will become the background color).
/// * A back color of `PaletteColor::HighlightText` (will become the front color).
///
/// From there, the `PaletteColor::Highlight` and `PaletteColor::HighlightText` will be resolved to
/// concrete colors (or possibly to `InheritParent`, which will inherit the previous concrete
/// color).
///
/// To override the look of highlighted text, you can either:
/// * Change the palette entries for `PaletteColor::Highlight`/`PaletteColor::HighlightText`.
/// * Change the palette entry for `PaletteStyle::Highlight`, possibly using different palette
/// colors instead (or directly specifying a concrete color there).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Enum)]
pub enum PaletteStyle {
/// Style used for regular text.
Expand All @@ -380,8 +404,17 @@ pub enum PaletteStyle {
Highlight,
/// Style used for inactive highlighted text.
HighlightInactive,
/// Style used to draw the shadows.

/// Style used to draw the drop shadows (1-cell border to the bottom/right
/// of views).
Shadow,

/// Style used for editable text (TextArea, EditView).
EditableText,
/// Style used for the selected character in editable text.
EditableTextCursor,
/// Style used for editable text when inactive.
EditableTextInactive,
}

impl PaletteStyle {
Expand Down Expand Up @@ -415,6 +448,7 @@ impl FromStr for PaletteStyle {
use PaletteStyle::*;

Ok(match s {
// TODO: make a macro for this?
"Background" | "background" => Background,
"Shadow" | "shadow" => Shadow,
"View" | "view" => View,
Expand All @@ -425,6 +459,9 @@ impl FromStr for PaletteStyle {
"TitleSecondary" | "title_secondary" => TitleSecondary,
"Highlight" | "highlight" => Highlight,
"HighlightInactive" | "highlight_inactive" => HighlightInactive,
"EditableText" | "editable_text" => EditableText,
"EditableTextCursor" | "editable_text_cursor" => EditableTextCursor,
"EditableTextInactive" | "editable_text_inactive" => EditableTextInactive,
_ => return Err(NoSuchColor),
})
}
Expand Down
150 changes: 81 additions & 69 deletions cursive-core/src/views/edit_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ pub struct EditView {

enabled: bool,

style: StyleType,
regular_style: StyleType,
inactive_style: StyleType,
cursor_style: StyleType,
}

new_default!(EditView);
Expand All @@ -126,7 +128,9 @@ impl EditView {
secret: false,
filler: "_".to_string(),
enabled: true,
style: PaletteStyle::Secondary.into(),
regular_style: PaletteStyle::EditableText.into(),
inactive_style: PaletteStyle::EditableTextInactive.into(),
cursor_style: PaletteStyle::EditableTextCursor.into(),
}
}

Expand Down Expand Up @@ -192,7 +196,9 @@ impl EditView {
///
/// Defaults to `ColorStyle::Secondary`.
pub fn set_style<S: Into<StyleType>>(&mut self, style: S) {
self.style = style.into();
let style = style.into();
self.regular_style = style;
// TODO: Also update cursor and inactive styles?
}

/// Sets the style used for this view.
Expand Down Expand Up @@ -503,6 +509,11 @@ impl EditView {
/// Best used for single character replacement.
fn make_small_stars(length: usize) -> &'static str {
// TODO: be able to use any character as hidden mode?
assert!(
length <= 4,
"Can only generate stars for one grapheme at a time."
);

&"****"[..length]
}

Expand All @@ -514,80 +525,81 @@ impl View for EditView {
self.last_length, printer.size.x
);

let (style, cursor_style) = if self.enabled && printer.enabled {
(self.regular_style, self.cursor_style)
} else {
(self.inactive_style, self.inactive_style)
};

let width = self.content.width();
printer.with_style(self.style, |printer| {
let effect = if self.enabled && printer.enabled {
Effect::Reverse
printer.with_style(style, |printer| {
if width < self.last_length {
// No problem, everything fits.
assert!(printer.size.x >= width);
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), &self.content);
}
let filler_len = (printer.size.x - width) / self.filler.width();
printer.print_hline((width, 0), filler_len, self.filler.as_str());
} else {
Effect::Simple
};
printer.with_effect(effect, |printer| {
let content = &self.content[self.offset..];
let display_bytes = content
.graphemes(true)
.scan(0, |w, g| {
*w += g.width();
if *w > self.last_length {
None
} else {
Some(g)
}
})
.map(str::len)
.sum();

let content = &content[..display_bytes];
let width = content.width();

if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), content);
}

if width < self.last_length {
// No problem, everything fits.
assert!(printer.size.x >= width);
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), &self.content);
}
let filler_len = (printer.size.x - width) / self.filler.width();
let filler_len = (self.last_length - width) / self.filler.width();
printer.print_hline((width, 0), filler_len, self.filler.as_str());
} else {
let content = &self.content[self.offset..];
let display_bytes = content
.graphemes(true)
.scan(0, |w, g| {
*w += g.width();
if *w > self.last_length {
None
} else {
Some(g)
}
})
.map(str::len)
.sum();

let content = &content[..display_bytes];
let width = content.width();

if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), content);
}

if width < self.last_length {
let filler_len = (self.last_length - width) / self.filler.width();
printer.print_hline((width, 0), filler_len, self.filler.as_str());
}
}
});
}
});

// Now print cursor
if printer.focused {
let c: &str = if self.cursor == self.content.len() {
&self.filler
// Now print cursor
if printer.focused {
let c: &str = if self.cursor == self.content.len() {
&self.filler
} else {
// Get the char from the string... Is it so hard?
let selected = self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap_or_else(|| {
panic!(
"Found no char at cursor {} in {}",
self.cursor, &self.content
)
});
if self.secret {
make_small_stars(selected.width())
} else {
// Get the char from the string... Is it so hard?
let selected = self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap_or_else(|| {
panic!(
"Found no char at cursor {} in {}",
self.cursor, &self.content
)
});
if self.secret {
make_small_stars(selected.width())
} else {
selected
}
};
let offset = self.content[self.offset..self.cursor].width();
selected
}
};
let offset = self.content[self.offset..self.cursor].width();
printer.with_style(cursor_style, |printer| {
printer.print((offset, 0), c);
}
});
});
}
}

fn layout(&mut self, size: Vec2) {
Expand Down
83 changes: 43 additions & 40 deletions cursive-core/src/views/text_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,49 +487,52 @@ impl View for TextArea {
}

fn draw(&self, printer: &Printer) {
printer.with_style(PaletteStyle::Secondary, |printer| {
let effect = if self.enabled && printer.enabled {
Effect::Reverse
} else {
Effect::Simple
};

let w = if self.scrollbase.scrollable() {
printer.size.x.saturating_sub(1)
} else {
printer.size.x
};
printer.with_effect(effect, |printer| {
for y in 0..printer.size.y {
printer.print_hline((0, y), w, " ");
}
});
let (style, cursor_style) = if self.enabled && printer.enabled {
(PaletteStyle::EditableText, PaletteStyle::EditableTextCursor)
} else {
(
PaletteStyle::EditableTextInactive,
PaletteStyle::EditableTextInactive,
)
};

debug!("Content: `{}`", &self.content);
self.scrollbase.draw(printer, |printer, i| {
debug!("Drawing row {}", i);
let row = &self.rows[i];
debug!("row: {:?}", row);
let text = &self.content[row.start..row.end];
debug!("row text: `{}`", text);
printer.with_effect(effect, |printer| {
printer.print((0, 0), text);
});
let w = if self.scrollbase.scrollable() {
printer.size.x.saturating_sub(1)
} else {
printer.size.x
};
printer.with_style(style, |printer| {
for y in 0..printer.size.y {
printer.print_hline((0, y), w, " ");
}
});

if printer.focused && i == self.selected_row() {
let cursor_offset = self.cursor - row.start;
let c = if cursor_offset == text.len() {
"_"
} else {
text[cursor_offset..]
.graphemes(true)
.next()
.expect("Found no char!")
};
let offset = text[..cursor_offset].width();
printer.print((offset, 0), c);
}
debug!("Content: `{}`", &self.content);
self.scrollbase.draw(printer, |printer, i| {
debug!("Drawing row {}", i);
let row = &self.rows[i];
debug!("row: {:?}", row);
let text = &self.content[row.start..row.end];
debug!("row text: `{}`", text);
printer.with_style(style, |printer| {
printer.print((0, 0), text);
});

if printer.focused && i == self.selected_row() {
let cursor_offset = self.cursor - row.start;
let c = if cursor_offset == text.len() {
"_"
} else {
text[cursor_offset..]
.graphemes(true)
.next()
.expect("Found no char!")
};
let offset = text[..cursor_offset].width();
printer.with_style(cursor_style, |printer| {
printer.print((offset, 0), c);
});
}
});
}

Expand Down
Loading

0 comments on commit e14706e

Please sign in to comment.