use tui_lipan::prelude::*;
use tui_lipan::{IMAGE_SENTINEL_BASE, ImageContent, ImageFormat, TextAreaImageMode};
struct ImageModesDemo;
#[derive(Default)]
struct State {
inline_value: String,
inline_cursor: usize,
inline_anchor: Option<usize>,
inline_images: Vec<ImageContent>,
attach_value: String,
attach_cursor: usize,
attach_anchor: Option<usize>,
attach_images: Vec<ImageContent>,
}
#[derive(Clone, Debug)]
enum Msg {
InlineChanged(TextAreaEvent),
InlineImagesChanged(Vec<ImageContent>),
AttachChanged(TextAreaEvent),
AttachImagesChanged(Vec<ImageContent>),
RemoveAttachment(DraggableTabCloseEvent),
SeedInline,
SeedAttach,
ClearInline,
ClearAttach,
}
fn dummy_image() -> ImageContent {
let png: &[u8] = &[
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44,
0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90,
0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8,
0xCF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x36, 0x28, 0x19, 0x00, 0x00, 0x00, 0x00,
0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
];
ImageContent::from_bytes(png, ImageFormat::Png)
}
impl Component for ImageModesDemo {
type Message = Msg;
type Properties = ();
type State = State;
fn create_state(&self, _props: &Self::Properties) -> Self::State {
State::default()
}
fn update(&mut self, msg: Msg, ctx: &mut Context<Self>) -> Update {
match msg {
Msg::InlineChanged(ev) => {
ctx.state.inline_value = ev.value.to_string();
ctx.state.inline_cursor = ev.cursor;
ctx.state.inline_anchor = ev.anchor;
}
Msg::InlineImagesChanged(imgs) => {
ctx.state.inline_images = imgs;
}
Msg::AttachChanged(ev) => {
ctx.state.attach_value = ev.value.to_string();
ctx.state.attach_cursor = ev.cursor;
ctx.state.attach_anchor = ev.anchor;
}
Msg::AttachImagesChanged(imgs) => {
ctx.state.attach_images = imgs;
}
Msg::RemoveAttachment(ev) => {
if ev.index < ctx.state.attach_images.len() {
ctx.state.attach_images.remove(ev.index);
}
}
Msg::SeedInline => {
let img = dummy_image();
let s0 = char::from_u32(IMAGE_SENTINEL_BASE as u32).unwrap();
let s1 = char::from_u32(IMAGE_SENTINEL_BASE as u32 + 1).unwrap();
let text = format!("Before {s0} middle {s1} after");
ctx.state.inline_cursor = text.len();
ctx.state.inline_value = text;
ctx.state.inline_anchor = None;
ctx.state.inline_images = vec![img.clone(), img];
}
Msg::SeedAttach => {
let img = dummy_image();
ctx.state.attach_value = "Caption for attached images".into();
ctx.state.attach_cursor = ctx.state.attach_value.len();
ctx.state.attach_anchor = None;
ctx.state.attach_images = vec![img.clone(), img.clone(), img];
}
Msg::ClearInline => {
ctx.state.inline_value.clear();
ctx.state.inline_cursor = 0;
ctx.state.inline_anchor = None;
ctx.state.inline_images.clear();
}
Msg::ClearAttach => {
ctx.state.attach_value.clear();
ctx.state.attach_cursor = 0;
ctx.state.attach_anchor = None;
ctx.state.attach_images.clear();
}
}
Update::full()
}
fn view(&self, ctx: &Context<Self>) -> Element {
let inline_status = if ctx.state.inline_images.is_empty() {
"No images".to_string()
} else {
format!("{} image(s) inline", ctx.state.inline_images.len())
};
let attach_status = if ctx.state.attach_images.is_empty() {
"No images".to_string()
} else {
format!("{} image(s) attached", ctx.state.attach_images.len())
};
let attach_chip_bar = if !ctx.state.attach_images.is_empty() {
Some(
DraggableTabBar::new()
.tabs(ctx.state.attach_images.iter().enumerate().map(|(i, _)| {
DraggableTab::new(format!("Photo {}", i + 1)).closeable(true)
}))
.active(usize::MAX) .style(Style::new().fg(Color::Green))
.active_style(Style::new().fg(Color::Green))
.close_style(Style::new().fg(Color::indexed(246)).dim())
.close_hover_style(Style::new().fg(Color::LightRed).bold())
.draggable(false)
.focusable(false)
.scroll_wheel(true)
.show_overflow_controls(true)
.on_close(ctx.link().callback(Msg::RemoveAttachment)),
)
} else {
None
};
rsx! {
Frame {
header_left: "TextArea Image Modes",
padding: 1,
HStack {
gap: 2,
VStack {
gap: 1,
Text {
content: "Inline Mode",
style: Style::new().bold().fg(Color::Cyan),
},
Text {
content: "Sentinels embedded in text value.",
style: Style::new().dim(),
},
TextArea {
value: ctx.state.inline_value.clone(),
cursor: ctx.state.inline_cursor,
anchor: ctx.state.inline_anchor,
placeholder: "Type or Ctrl+V to paste image inline...",
border: true,
border_style: BorderStyle::Rounded,
height: Length::Px(8),
wrap: true,
scrollbar: false,
on_change: ctx.link().callback(Msg::InlineChanged),
image_mode: TextAreaImageMode::Inline,
images: ctx.state.inline_images.clone(),
on_images_change: ctx.link().callback(Msg::InlineImagesChanged),
image_placeholder: "[Img]",
image_placeholder_style: Style::new().fg(Color::Magenta).bold(),
image_placeholder_focus_style: Style::new().fg(Color::LightMagenta).bold(),
},
Text {
content: inline_status,
style: Style::new().dim(),
},
HStack {
height: Length::Auto,
gap: 1,
Button {
label: "Seed",
on_click: ctx.link().callback(|_| Msg::SeedInline),
variant: ButtonVariant::Outlined,
style: Style::new().fg(Color::Cyan),
},
Button {
label: "Clear",
on_click: ctx.link().callback(|_| Msg::ClearInline),
variant: ButtonVariant::Outlined,
style: Style::new().fg(Color::Red),
},
},
},
Divider {
orientation: Orientation::Vertical,
join_frame: true,
},
VStack {
gap: 1,
Text {
content: "Attachment Mode",
style: Style::new().bold().fg(Color::Green),
},
Text {
content: "Chip bar (DraggableTabBar) above text.",
style: Style::new().dim(),
},
VStack {
gap: 0,
height: Length::Auto,
if let Some(bar) = attach_chip_bar {
bar,
},
TextArea {
value: ctx.state.attach_value.clone(),
cursor: ctx.state.attach_cursor,
anchor: ctx.state.attach_anchor,
placeholder: "Type or Ctrl+V to attach image...",
border: true,
border_style: BorderStyle::Rounded,
height: Length::Px(8),
wrap: true,
scrollbar: false,
on_change: ctx.link().callback(Msg::AttachChanged),
image_mode: TextAreaImageMode::Attachment,
images: ctx.state.attach_images.clone(),
on_images_change: ctx.link().callback(Msg::AttachImagesChanged),
},
},
Text {
content: attach_status,
style: Style::new().dim(),
},
HStack {
height: Length::Auto,
gap: 1,
Button {
label: "Seed",
on_click: ctx.link().callback(|_| Msg::SeedAttach),
variant: ButtonVariant::Outlined,
style: Style::new().fg(Color::Green),
},
Button {
label: "Clear",
on_click: ctx.link().callback(|_| Msg::ClearAttach),
variant: ButtonVariant::Outlined,
style: Style::new().fg(Color::Red),
},
},
},
},
}
}
}
}
fn main() -> Result<()> {
App::new().mount(ImageModesDemo).run()
}