use iced::widget::{column, container, row, scrollable, text};
use iced::{Element, Length, Subscription, Task};
use iced_markdown_editor::{
Action, Command, EditorState, Event, FONTS, ImageSettings, Output, Paper, markdown_editor,
};
use std::path::PathBuf;
pub fn main() -> iced::Result {
let mut app = iced::application(App::new, App::update, App::view)
.title("Embedded images")
.subscription(App::subscription)
.window_size((1300.0, 820.0));
for font in FONTS {
app = app.font(font);
}
app.run()
}
struct App {
editor: EditorState,
status: String,
preview: String,
size: usize,
}
#[derive(Debug, Clone)]
enum Message {
Editor(Action),
Picked(Option<PathBuf>),
}
impl App {
fn new() -> Self {
let editor = EditorState::with_markdown(
"# Embedded images\n\nPaste, drop or insert an image (up to 512 KiB). It is \
stored inside the Markdown as a `data:` URL.\n",
)
.images(ImageSettings::embed().max_bytes(512 * 1024).accept([
"image/png",
"image/jpeg",
"image/gif",
]))
.paper(Paper::fill());
let mut app = Self {
editor,
status: String::new(),
preview: String::new(),
size: 0,
};
app.refresh();
app
}
fn refresh(&mut self) {
let markdown = self.editor.markdown();
self.size = markdown.len();
self.preview = markdown
.lines()
.map(|line| {
let count = line.chars().count();
if count > 120 {
let short: String = line.chars().take(120).collect();
format!("{short}… ({count} characters)")
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n");
}
fn handle(&mut self, output: Output) -> Task<Message> {
let mut tasks = vec![output.task.map(Message::Editor)];
for event in output.events {
match event {
Event::ImageRejected { source, reason } => {
self.status = format!("{source:?}: {reason}");
}
Event::ImageRequested => tasks.push(Task::perform(
async {
rfd::AsyncFileDialog::new()
.add_filter("Images", &["png", "jpg", "jpeg", "gif"])
.pick_file()
.await
.map(|file| file.path().to_path_buf())
},
Message::Picked,
)),
Event::Changed => {
self.status.clear();
self.refresh();
}
_ => {}
}
}
Task::batch(tasks)
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Editor(action) => {
let output = self.editor.update(action);
self.handle(output)
}
Message::Picked(Some(path)) => {
let output = self.editor.execute(Command::InsertImageFile(path));
self.handle(output)
}
Message::Picked(None) => Task::none(),
}
}
fn view(&self) -> Element<'_, Message> {
let side = container(scrollable(
column![
text(format!("Markdown: {} bytes", self.size)).size(12),
text(&self.status).size(12),
text(&self.preview).size(12),
]
.spacing(8),
))
.padding(12)
.width(Length::Fixed(420.0))
.height(Length::Fill);
row![
markdown_editor(&self.editor).view().map(Message::Editor),
side
]
.into()
}
fn subscription(&self) -> Subscription<Message> {
self.editor.subscription().map(Message::Editor)
}
}