#[path = "scratch.rs"]
#[allow(dead_code)]
mod scratch;
use std::fs::File;
use std::task::{Context, Poll, Waker};
use iced::advanced::widget::Operation;
use iced::advanced::{clipboard, mouse, renderer};
use iced::keyboard::key::{Key, Named, NativeCode, Physical};
use iced::keyboard::{Event as KeyboardEvent, Location, Modifiers};
use iced::{Event, Font, Pixels, Point, Size, Theme};
use iced_runtime::user_interface::{Cache, UserInterface};
use iced_runtime::{task, Action};
use iced_tiny_skia::graphics::Viewport;
use scratch::{scrive_dark, App, Message};
use scrive_iced::Event as CeEvent;
const W: u32 = 900;
const H: u32 = 560;
enum Step {
Ev(Event),
Msg(Message),
}
fn key_event(key: Key, text: Option<&str>, modifiers: Modifiers) -> Event {
Event::Keyboard(KeyboardEvent::KeyPressed {
key: key.clone(),
modified_key: key,
physical_key: Physical::Unidentified(NativeCode::Unidentified),
location: Location::Standard,
modifiers,
text: text.map(Into::into),
repeat: false,
})
}
fn typed(c: &str) -> Step {
Step::Ev(key_event(Key::Character(c.into()), Some(c), Modifiers::empty()))
}
fn press(named: Named, modifiers: Modifiers) -> Step {
Step::Ev(key_event(Key::Named(named), None, modifiers))
}
fn chord(c: &str, modifiers: Modifiers) -> Step {
Step::Ev(key_event(Key::Character(c.into()), None, modifiers | Modifiers::CTRL))
}
fn script() -> Vec<(Vec<Step>, u16)> {
let mut t: Vec<(Vec<Step>, u16)> = Vec::new();
let mut beat = |steps: Vec<Step>, hold: u16| t.push((steps, hold));
beat(vec![], 110);
for _ in 0..7 {
beat(vec![press(Named::ArrowDown, Modifiers::empty())], 9);
}
for _ in 0..6 {
beat(vec![press(Named::ArrowRight, Modifiers::empty())], 8);
}
beat(vec![], 45);
for _ in 0..7 {
beat(vec![chord("d", Modifiers::empty())], 27);
}
beat(vec![], 85);
for c in ["b", "u", "f", "f", "e", "r"] {
beat(vec![typed(c)], 14);
}
beat(vec![], 100);
beat(vec![press(Named::Escape, Modifiers::empty())], 45);
beat(vec![press(Named::Home, Modifiers::CTRL)], 20);
beat(vec![Step::Msg(Message::Editor(CeEvent::OpenReplace))], 34);
for q in ["r", "re", "rea", "read", "readi", "readin", "reading"] {
beat(vec![Step::Msg(Message::Editor(CeEvent::FindQuery(q.into())))], 11);
}
beat(vec![], 55);
beat(vec![Step::Msg(Message::Editor(CeEvent::FindNext))], 46);
beat(vec![Step::Msg(Message::Editor(CeEvent::FindNext))], 46);
beat(vec![], 30);
for r in ["s", "sa", "sam", "samp", "sampl", "sample"] {
beat(vec![Step::Msg(Message::Editor(CeEvent::ReplaceText(r.into())))], 12);
}
beat(vec![], 55);
beat(vec![Step::Msg(Message::Editor(CeEvent::ReplaceAll))], 85);
beat(vec![Step::Msg(Message::Editor(CeEvent::CloseFind))], 18);
beat(vec![], 120);
beat(vec![chord("z", Modifiers::empty())], 55);
beat(vec![], 35);
beat(vec![press(Named::Home, Modifiers::CTRL)], 25);
for _ in 0..12 {
beat(vec![press(Named::ArrowDown, Modifiers::empty())], 7);
}
beat(vec![], 35);
beat(vec![chord("[", Modifiers::empty())], 95);
beat(vec![chord("]", Modifiers::empty())], 80);
beat(vec![], 55);
beat(vec![press(Named::End, Modifiers::empty())], 22);
beat(vec![press(Named::Enter, Modifiers::empty())], 22);
for c in ["s", "t", "r"] {
beat(vec![typed(c)], 16);
}
beat(vec![], 90);
beat(vec![press(Named::ArrowDown, Modifiers::empty())], 32);
beat(vec![press(Named::Escape, Modifiers::empty())], 25);
beat(vec![chord("z", Modifiers::empty())], 30);
beat(vec![press(Named::Home, Modifiers::CTRL)], 20);
beat(vec![], 150);
t
}
fn drain(task: iced::Task<Message>, inbox: &mut Vec<Message>, ops: &mut Vec<Box<dyn Operation>>) {
let Some(mut stream) = task::into_stream(task) else {
return;
};
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
loop {
match stream.as_mut().poll_next(&mut cx) {
Poll::Ready(Some(Action::Output(msg))) => inbox.push(msg),
Poll::Ready(Some(Action::Widget(op))) => ops.push(op),
Poll::Ready(Some(_)) => {}
Poll::Ready(None) | Poll::Pending => break,
}
}
}
fn main() {
let out = std::env::args().nth(1).unwrap_or_else(|| "showcase.gif".into());
let mut fs = iced_tiny_skia::graphics::text::font_system()
.write()
.expect("font system lock");
for font in scrive_iced::required_fonts() {
fs.load_font(std::borrow::Cow::Borrowed(font));
}
drop(fs);
let mut renderer = iced_renderer::fallback::Renderer::Secondary(
iced_tiny_skia::Renderer::new(Font::default(), Pixels(14.0)),
);
let viewport = Viewport::with_physical_size(Size::new(W, H), 1.0);
let logical = Size::new(W as f32, H as f32);
let cursor = mouse::Cursor::Available(Point::new(W as f32 / 2.0, H as f32 / 2.0));
let theme: Theme = scrive_dark();
let mut app = App::default();
let mut cache = Cache::new();
let mut inbox: Vec<Message> = Vec::new();
let mut ops: Vec<Box<dyn Operation>> = Vec::new();
let file = File::create(&out).expect("create GIF file");
let mut encoder =
gif::Encoder::new(std::io::BufWriter::new(file), W as u16, H as u16, &[]).expect("gif encoder");
encoder.set_repeat(gif::Repeat::Infinite).expect("gif repeat");
let mut pending: Option<(Vec<u8>, u16)> = None;
let mut frames = 0u32;
{
let events = [Event::Window(iced::window::Event::RedrawRequested(
std::time::Instant::now(),
))];
let mut ui: UserInterface<'_, Message, Theme, iced::Renderer> =
UserInterface::build(app.view(), logical, cache, &mut renderer);
let mut published: Vec<Message> = Vec::new();
let _ = ui.update(&events, cursor, &mut renderer, &mut clipboard::Null, &mut published);
cache = ui.into_cache();
inbox.extend(published);
while !inbox.is_empty() {
for m in std::mem::take(&mut inbox) {
drain(app.update(m), &mut inbox, &mut ops);
}
}
}
for (steps, hold) in script() {
let mut events: Vec<Event> = vec![Event::Window(iced::window::Event::RedrawRequested(
std::time::Instant::now(),
))];
for step in steps {
match step {
Step::Ev(e) => events.push(e),
Step::Msg(m) => inbox.push(m),
}
}
while !inbox.is_empty() {
for m in std::mem::take(&mut inbox) {
drain(app.update(m), &mut inbox, &mut ops);
}
}
let mut ui: UserInterface<'_, Message, Theme, iced::Renderer> =
UserInterface::build(app.view(), logical, cache, &mut renderer);
for mut op in ops.drain(..) {
ui.operate(&renderer, op.as_mut());
}
let mut published: Vec<Message> = Vec::new();
let _ = ui.update(&events, cursor, &mut renderer, &mut clipboard::Null, &mut published);
ui.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
cache = ui.into_cache();
inbox.extend(published);
while !inbox.is_empty() {
for m in std::mem::take(&mut inbox) {
drain(app.update(m), &mut inbox, &mut ops);
}
}
let iced_renderer::fallback::Renderer::Secondary(ts) = &mut renderer else {
unreachable!("constructed the tiny-skia variant above");
};
let rgba = iced_tiny_skia::window::compositor::screenshot(ts, &viewport, theme.palette().background);
match &mut pending {
Some((prev, d)) if *prev == rgba => *d = d.saturating_add(hold),
_ => {
if let Some((prev, d)) = pending.take() {
write_frame(&mut encoder, prev, d);
frames += 1;
}
pending = Some((rgba, hold));
}
}
}
if let Some((prev, d)) = pending.take() {
write_frame(&mut encoder, prev, d);
frames += 1;
}
drop(encoder);
eprintln!("wrote {out} ({frames} frames, {W}x{H})");
}
fn write_frame<W: std::io::Write>(encoder: &mut gif::Encoder<W>, mut rgba: Vec<u8>, delay: u16) {
let mut frame = gif::Frame::from_rgba_speed(W as u16, H as u16, &mut rgba, 10);
frame.delay = delay;
encoder.write_frame(&frame).expect("write gif frame");
}