mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 23:42:59 +00:00
feat: allow using images on right in footer
This commit is contained in:
parent
f59c19af36
commit
93359444de
@ -1,4 +1,6 @@
|
|||||||
use super::{RenderError, RenderResult, layout::Layout, properties::CursorPosition, text::TextDrawer};
|
use super::{
|
||||||
|
RenderError, RenderResult, layout::Layout, operation::ImagePosition, properties::CursorPosition, text::TextDrawer,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{MaxColumnsAlignment, MaxRowsAlignment},
|
config::{MaxColumnsAlignment, MaxRowsAlignment},
|
||||||
markdown::{text::WeightedLine, text_style::Colors},
|
markdown::{text::WeightedLine, text_style::Colors},
|
||||||
@ -274,9 +276,10 @@ where
|
|||||||
(image_scale.columns, image_scale.rows)
|
(image_scale.columns, image_scale.rows)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let cursor = match properties.center {
|
let cursor = match &properties.position {
|
||||||
true => Self::center_cursor(columns, &rect.dimensions, &starting_cursor),
|
ImagePosition::Cursor => starting_cursor.clone(),
|
||||||
false => starting_cursor.clone(),
|
ImagePosition::Center => Self::center_cursor(columns, &rect.dimensions, &starting_cursor),
|
||||||
|
ImagePosition::Right => Self::align_cursor_right(columns, &rect.dimensions, &starting_cursor),
|
||||||
};
|
};
|
||||||
self.terminal.execute(&TerminalCommand::MoveToColumn(cursor.column))?;
|
self.terminal.execute(&TerminalCommand::MoveToColumn(cursor.column))?;
|
||||||
|
|
||||||
@ -303,6 +306,11 @@ where
|
|||||||
CursorPosition { row: cursor.row, column: start_column }
|
CursorPosition { row: cursor.row, column: start_column }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn align_cursor_right(columns: u16, window: &WindowSize, cursor: &CursorPosition) -> CursorPosition {
|
||||||
|
let start_column = window.columns.saturating_sub(columns).saturating_add(cursor.column);
|
||||||
|
CursorPosition { row: cursor.row, column: start_column }
|
||||||
|
}
|
||||||
|
|
||||||
fn render_block_line(&mut self, operation: &BlockLine) -> RenderResult {
|
fn render_block_line(&mut self, operation: &BlockLine) -> RenderResult {
|
||||||
let BlockLine {
|
let BlockLine {
|
||||||
text,
|
text,
|
||||||
@ -806,8 +814,13 @@ mod tests {
|
|||||||
fn image(#[case] size: ImageSize) {
|
fn image(#[case] size: ImageSize) {
|
||||||
let image = DynamicImage::new(2, 2, ColorType::Rgba8);
|
let image = DynamicImage::new(2, 2, ColorType::Rgba8);
|
||||||
let image = Image::new(TerminalImage::Ascii(image.into()), ImageSource::Generated);
|
let image = Image::new(TerminalImage::Ascii(image.into()), ImageSource::Generated);
|
||||||
let properties =
|
let properties = ImageRenderProperties {
|
||||||
ImageRenderProperties { z_index: 0, size, restore_cursor: false, background_color: None, center: false };
|
z_index: 0,
|
||||||
|
size,
|
||||||
|
restore_cursor: false,
|
||||||
|
background_color: None,
|
||||||
|
position: ImagePosition::Cursor,
|
||||||
|
};
|
||||||
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
||||||
let expected = [
|
let expected = [
|
||||||
// centered 20x10, the image is 2x2 so we stand one away from center
|
// centered 20x10, the image is 2x2 so we stand one away from center
|
||||||
@ -835,8 +848,13 @@ mod tests {
|
|||||||
fn centered_image(#[case] size: ImageSize) {
|
fn centered_image(#[case] size: ImageSize) {
|
||||||
let image = DynamicImage::new(2, 2, ColorType::Rgba8);
|
let image = DynamicImage::new(2, 2, ColorType::Rgba8);
|
||||||
let image = Image::new(TerminalImage::Ascii(image.into()), ImageSource::Generated);
|
let image = Image::new(TerminalImage::Ascii(image.into()), ImageSource::Generated);
|
||||||
let properties =
|
let properties = ImageRenderProperties {
|
||||||
ImageRenderProperties { z_index: 0, size, restore_cursor: false, background_color: None, center: true };
|
z_index: 0,
|
||||||
|
size,
|
||||||
|
restore_cursor: false,
|
||||||
|
background_color: None,
|
||||||
|
position: ImagePosition::Center,
|
||||||
|
};
|
||||||
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
||||||
let expected = [
|
let expected = [
|
||||||
// centered 20x10, the image is 2x2 so we stand one away from center
|
// centered 20x10, the image is 2x2 so we stand one away from center
|
||||||
@ -856,6 +874,40 @@ mod tests {
|
|||||||
assert_eq!(ops, expected);
|
assert_eq!(ops, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same as the above but use right alignment
|
||||||
|
#[rstest]
|
||||||
|
#[case::shrink(ImageSize::ShrinkIfNeeded)]
|
||||||
|
#[case::specific(ImageSize::Specific(2, 2))]
|
||||||
|
#[case::width_scaled(ImageSize::WidthScaled { ratio: 1.0 })]
|
||||||
|
fn right_aligned_image(#[case] size: ImageSize) {
|
||||||
|
let image = DynamicImage::new(2, 2, ColorType::Rgba8);
|
||||||
|
let image = Image::new(TerminalImage::Ascii(image.into()), ImageSource::Generated);
|
||||||
|
let properties = ImageRenderProperties {
|
||||||
|
z_index: 0,
|
||||||
|
size,
|
||||||
|
restore_cursor: false,
|
||||||
|
background_color: None,
|
||||||
|
position: ImagePosition::Right,
|
||||||
|
};
|
||||||
|
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
||||||
|
let expected = [
|
||||||
|
// right aligned 20x10, the image is 2x2 so we stand one away from the right
|
||||||
|
Instruction::MoveTo(40, 45),
|
||||||
|
Instruction::MoveToColumn(58),
|
||||||
|
Instruction::PrintImage(PrintOptions {
|
||||||
|
columns: 2,
|
||||||
|
rows: 2,
|
||||||
|
z_index: 0,
|
||||||
|
background_color: None,
|
||||||
|
column_width: 2,
|
||||||
|
row_height: 2,
|
||||||
|
}),
|
||||||
|
// place cursor after the image
|
||||||
|
Instruction::MoveToRow(47),
|
||||||
|
];
|
||||||
|
assert_eq!(ops, expected);
|
||||||
|
}
|
||||||
|
|
||||||
// same as the above but center it
|
// same as the above but center it
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn restore_cursor_after_image() {
|
fn restore_cursor_after_image() {
|
||||||
@ -866,7 +918,7 @@ mod tests {
|
|||||||
size: ImageSize::ShrinkIfNeeded,
|
size: ImageSize::ShrinkIfNeeded,
|
||||||
restore_cursor: true,
|
restore_cursor: true,
|
||||||
background_color: None,
|
background_color: None,
|
||||||
center: true,
|
position: ImagePosition::Center,
|
||||||
};
|
};
|
||||||
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
let ops = render_with_max_size(&[RenderOperation::RenderImage(image, properties)]);
|
||||||
let expected = [
|
let expected = [
|
||||||
|
@ -101,7 +101,7 @@ pub(crate) struct ImageRenderProperties {
|
|||||||
pub(crate) size: ImageSize,
|
pub(crate) size: ImageSize,
|
||||||
pub(crate) restore_cursor: bool,
|
pub(crate) restore_cursor: bool,
|
||||||
pub(crate) background_color: Option<Color>,
|
pub(crate) background_color: Option<Color>,
|
||||||
pub(crate) center: bool,
|
pub(crate) position: ImagePosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ImageRenderProperties {
|
impl Default for ImageRenderProperties {
|
||||||
@ -111,11 +111,18 @@ impl Default for ImageRenderProperties {
|
|||||||
size: Default::default(),
|
size: Default::default(),
|
||||||
restore_cursor: false,
|
restore_cursor: false,
|
||||||
background_color: None,
|
background_color: None,
|
||||||
center: true,
|
position: ImagePosition::Cursor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub(crate) enum ImagePosition {
|
||||||
|
Cursor,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
/// The size used when printing an image.
|
/// The size used when printing an image.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub(crate) enum ImageSize {
|
pub(crate) enum ImageSize {
|
||||||
|
@ -474,7 +474,7 @@ pub(crate) enum FooterStyle {
|
|||||||
Template {
|
Template {
|
||||||
left: Option<FooterContent>,
|
left: Option<FooterContent>,
|
||||||
center: Option<FooterContent>,
|
center: Option<FooterContent>,
|
||||||
right: Option<FooterTemplate>,
|
right: Option<FooterContent>,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
height: u16,
|
height: u16,
|
||||||
},
|
},
|
||||||
@ -496,7 +496,7 @@ impl FooterStyle {
|
|||||||
raw::FooterStyle::Template { left, center, right, colors, height } => {
|
raw::FooterStyle::Template { left, center, right, colors, height } => {
|
||||||
let left = left.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
|
let left = left.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
|
||||||
let center = center.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
|
let center = center.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
|
||||||
let right = right.clone();
|
let right = right.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
|
||||||
let style = TextStyle::colored(colors.resolve(palette)?);
|
let style = TextStyle::colored(colors.resolve(palette)?);
|
||||||
let height = height.unwrap_or(DEFAULT_FOOTER_HEIGHT);
|
let height = height.unwrap_or(DEFAULT_FOOTER_HEIGHT);
|
||||||
Ok(Self::Template { left, center, right, style, height })
|
Ok(Self::Template { left, center, right, style, height })
|
||||||
|
@ -412,7 +412,7 @@ pub(super) enum FooterStyle {
|
|||||||
center: Option<FooterContent>,
|
center: Option<FooterContent>,
|
||||||
|
|
||||||
/// The content to be put on the right.
|
/// The content to be put on the right.
|
||||||
right: Option<FooterTemplate>,
|
right: Option<FooterContent>,
|
||||||
|
|
||||||
/// The colors to be used.
|
/// The colors to be used.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
text_style::{TextStyle, UndefinedPaletteColorError},
|
text_style::{TextStyle, UndefinedPaletteColorError},
|
||||||
},
|
},
|
||||||
render::{
|
render::{
|
||||||
operation::{AsRenderOperations, ImageRenderProperties, MarginProperties, RenderOperation},
|
operation::{AsRenderOperations, ImagePosition, ImageRenderProperties, MarginProperties, RenderOperation},
|
||||||
properties::WindowSize,
|
properties::WindowSize,
|
||||||
},
|
},
|
||||||
terminal::image::Image,
|
terminal::image::Image,
|
||||||
@ -53,14 +53,8 @@ impl FooterGenerator {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_image(
|
fn push_image(&self, image: &Image, alignment: Alignment, operations: &mut Vec<RenderOperation>) {
|
||||||
&self,
|
let mut properties = ImageRenderProperties::default();
|
||||||
image: &Image,
|
|
||||||
alignment: Alignment,
|
|
||||||
dimensions: &WindowSize,
|
|
||||||
operations: &mut Vec<RenderOperation>,
|
|
||||||
) {
|
|
||||||
let mut properties = ImageRenderProperties { center: false, ..Default::default() };
|
|
||||||
|
|
||||||
operations.push(RenderOperation::ApplyMargin(MarginProperties {
|
operations.push(RenderOperation::ApplyMargin(MarginProperties {
|
||||||
horizontal: Margin::Fixed(0),
|
horizontal: Margin::Fixed(0),
|
||||||
@ -70,11 +64,12 @@ impl FooterGenerator {
|
|||||||
match alignment {
|
match alignment {
|
||||||
Alignment::Left { .. } => {
|
Alignment::Left { .. } => {
|
||||||
operations.push(RenderOperation::JumpToColumn { index: 0 });
|
operations.push(RenderOperation::JumpToColumn { index: 0 });
|
||||||
|
properties.position = ImagePosition::Cursor;
|
||||||
}
|
}
|
||||||
Alignment::Right { .. } => {
|
Alignment::Right { .. } => {
|
||||||
operations.push(RenderOperation::JumpToColumn { index: dimensions.columns.saturating_sub(1) });
|
properties.position = ImagePosition::Right;
|
||||||
}
|
}
|
||||||
Alignment::Center { .. } => properties.center = true,
|
Alignment::Center { .. } => properties.position = ImagePosition::Center,
|
||||||
};
|
};
|
||||||
operations.extend([
|
operations.extend([
|
||||||
// Start printing the image at the top of the footer rect
|
// Start printing the image at the top of the footer rect
|
||||||
@ -101,23 +96,20 @@ impl AsRenderOperations for FooterGenerator {
|
|||||||
let alignments = [
|
let alignments = [
|
||||||
Alignment::Left { margin: Default::default() },
|
Alignment::Left { margin: Default::default() },
|
||||||
Alignment::Center { minimum_size: 0, minimum_margin: Default::default() },
|
Alignment::Center { minimum_size: 0, minimum_margin: Default::default() },
|
||||||
|
Alignment::Right { margin: Default::default() },
|
||||||
];
|
];
|
||||||
for (content, alignment) in [left, center].iter().zip(alignments) {
|
for (content, alignment) in [left, center, right].iter().zip(alignments) {
|
||||||
if let Some(content) = content {
|
if let Some(content) = content {
|
||||||
match content {
|
match content {
|
||||||
RenderedFooterContent::Line(line) => {
|
RenderedFooterContent::Line(line) => {
|
||||||
Self::render_line(line, alignment, *height, &mut operations);
|
Self::render_line(line, alignment, *height, &mut operations);
|
||||||
}
|
}
|
||||||
RenderedFooterContent::Image(image) => {
|
RenderedFooterContent::Image(image) => {
|
||||||
self.push_image(image, alignment, dimensions, &mut operations);
|
self.push_image(image, alignment, &mut operations);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We don't support images on the right so treat this differently
|
|
||||||
if let Some(line) = right {
|
|
||||||
Self::render_line(line, Alignment::Right { margin: Default::default() }, *height, &mut operations);
|
|
||||||
}
|
|
||||||
operations.push(RenderOperation::PopMargin);
|
operations.push(RenderOperation::PopMargin);
|
||||||
operations
|
operations
|
||||||
}
|
}
|
||||||
@ -146,7 +138,7 @@ enum RenderedFooterStyle {
|
|||||||
Template {
|
Template {
|
||||||
left: Option<RenderedFooterContent>,
|
left: Option<RenderedFooterContent>,
|
||||||
center: Option<RenderedFooterContent>,
|
center: Option<RenderedFooterContent>,
|
||||||
right: Option<FooterLine>,
|
right: Option<RenderedFooterContent>,
|
||||||
height: u16,
|
height: u16,
|
||||||
},
|
},
|
||||||
ProgressBar {
|
ProgressBar {
|
||||||
@ -166,7 +158,7 @@ impl RenderedFooterStyle {
|
|||||||
FooterStyle::Template { left, center, right, style, height } => {
|
FooterStyle::Template { left, center, right, style, height } => {
|
||||||
let left = left.map(|c| RenderedFooterContent::new(c, &style, vars, palette)).transpose()?;
|
let left = left.map(|c| RenderedFooterContent::new(c, &style, vars, palette)).transpose()?;
|
||||||
let center = center.map(|c| RenderedFooterContent::new(c, &style, vars, palette)).transpose()?;
|
let center = center.map(|c| RenderedFooterContent::new(c, &style, vars, palette)).transpose()?;
|
||||||
let right = right.map(|c| FooterLine::new(c, &style, vars, palette)).transpose()?;
|
let right = right.map(|c| RenderedFooterContent::new(c, &style, vars, palette)).transpose()?;
|
||||||
Ok(Self::Template { left, center, right, height })
|
Ok(Self::Template { left, center, right, height })
|
||||||
}
|
}
|
||||||
FooterStyle::ProgressBar { character, style } => Ok(Self::ProgressBar { character, style }),
|
FooterStyle::ProgressBar { character, style } => Ok(Self::ProgressBar { character, style }),
|
||||||
|
@ -9,7 +9,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
presentation::PresentationState,
|
presentation::PresentationState,
|
||||||
render::{
|
render::{
|
||||||
operation::{AsRenderOperations, ImageRenderProperties, ImageSize, MarginProperties, RenderOperation},
|
operation::{
|
||||||
|
AsRenderOperations, ImagePosition, ImageRenderProperties, ImageSize, MarginProperties, RenderOperation,
|
||||||
|
},
|
||||||
properties::WindowSize,
|
properties::WindowSize,
|
||||||
},
|
},
|
||||||
terminal::image::Image,
|
terminal::image::Image,
|
||||||
@ -307,7 +309,7 @@ impl AsRenderOperations for CenterModalContent {
|
|||||||
size: ImageSize::Specific(self.content_width, content_height),
|
size: ImageSize::Specific(self.content_width, content_height),
|
||||||
restore_cursor: true,
|
restore_cursor: true,
|
||||||
background_color: None,
|
background_color: None,
|
||||||
center: true,
|
position: ImagePosition::Center,
|
||||||
};
|
};
|
||||||
operations.push(RenderOperation::RenderImage(image.clone(), properties));
|
operations.push(RenderOperation::RenderImage(image.clone(), properties));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user