Create a slideshow application in my cyberpunk style #252

Merged
savanni merged 15 commits from cybperpunk-billboard into main 2024-11-03 21:16:38 +00:00
4 changed files with 128 additions and 75 deletions
Showing only changes of commit 7b50a71369 - Show all commits

1
Cargo.lock generated
View File

@ -921,6 +921,7 @@ dependencies = [
name = "cyberpunk-slideshow" name = "cyberpunk-slideshow"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-std",
"cairo-rs", "cairo-rs",
"cyberpunk", "cyberpunk",
"gio 0.18.4", "gio 0.18.4",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-std = "1.13.0"
cairo-rs = "0.18" cairo-rs = "0.18"
cyberpunk = { path = "../cyberpunk" } cyberpunk = { path = "../cyberpunk" }
gio = "0.18" gio = "0.18"

View File

@ -1,11 +1,18 @@
- text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan - text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan
position: top position: top
transition: !Fade transition:
secs: 2
nanos: 0
- text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark. - text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark.
position: middle position: middle
transition: !Fade transition:
secs: 2
nanos: 0
- text: Electricity is the closest we get to Magic in this world. - text: Electricity is the closest we get to Magic in this world.
position: middle position: middle
transition: !Fade transition:
secs: 2
nanos: 0

View File

@ -1,5 +1,14 @@
use std::{cell::RefCell, fs::File, io::Read, ops::Index, path::Path, rc::Rc, time::Duration}; use std::{
cell::RefCell,
fs::File,
io::Read,
ops::Index,
path::Path,
rc::Rc,
time::{Duration, Instant},
};
use cairo::Context;
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text}; use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
use glib::{GString, Object}; use glib::{GString, Object};
use gtk::{ use gtk::{
@ -10,6 +19,7 @@ use gtk::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const FPS: u64 = 60;
const PURPLE: (f64, f64, f64) = (0.7, 0., 1.); const PURPLE: (f64, f64, f64) = (0.7, 0., 1.);
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -20,24 +30,11 @@ enum Position {
Bottom, Bottom,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
enum TransitionName {
Fade,
CrossFade,
}
struct Fade(Step);
enum Transition {
Fade(Fade),
CrossFade(Step, Step),
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
struct Step { struct Step {
text: String, text: String,
position: Position, position: Position,
transition: TransitionName, transition: Duration,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -75,6 +72,59 @@ impl Index<usize> for Script {
} }
} }
struct Fade {
text: String,
position: Position,
duration: Duration,
start_time: Instant,
}
/*
impl Fade {
fn render(&self, context: &Context) {
let start = Instant::now();
}
}
*/
/*
impl Transition {
fn render(&self, context: &Context) {
match self {
Transition::Fade(fade) => fade.render(context),
Transition::CrossFade(start, end) => {}
}
}
}
*/
trait Animation {
fn position(&self) -> Position;
fn tick(&self, now: Instant, context: &Context) -> bool;
}
impl Animation for Fade {
fn position(&self) -> Position {
self.position.clone()
}
fn tick(&self, now: Instant, context: &Context) -> bool {
let total_frames = self.duration.as_secs() * FPS;
let alpha_rate: f64 = 1. / total_frames as f64;
let frames = (now - self.start_time).as_secs_f64() * FPS as f64;
let alpha = alpha_rate * frames as f64;
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
let text_display = Text::new(self.text.clone(), context, 32.);
text_display.draw();
false
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct CyberScreenState { pub struct CyberScreenState {
script: Script, script: Script,
@ -103,13 +153,15 @@ impl CyberScreenState {
s s
} }
fn next_page(&mut self) -> Transition { fn next_page(&mut self) -> impl Animation {
let idx = match self.idx { let idx = match self.idx {
None => 0, None => 0,
Some(idx) => if idx < self.script.len() { Some(idx) => {
idx + 1 if idx < self.script.len() {
} else { idx + 1
idx } else {
idx
}
} }
}; };
self.idx = Some(idx); self.idx = Some(idx);
@ -117,40 +169,28 @@ impl CyberScreenState {
match step.position { match step.position {
Position::Top => { Position::Top => {
let transition = if let Some(old_step) = self.top.take() { self.top = Some(step.clone());
Transition::CrossFade(old_step, step.clone())
} else {
Transition::Fade(Fade(step.clone()))
};
self.top = Some(step);
transition
} }
Position::Middle => { Position::Middle => {
let transition = if let Some(old_step) = self.middle.take() { self.middle = Some(step.clone());
Transition::CrossFade(old_step, step.clone())
} else {
Transition::Fade(Fade(step.clone()))
};
self.middle = Some(step);
transition
} }
Position::Bottom => { Position::Bottom => {
let transition = if let Some(old_step) = self.bottom.take() { self.bottom = Some(step.clone());
Transition::CrossFade(old_step, step.clone())
} else {
Transition::Fade(Fade(step.clone()))
};
self.bottom = Some(step);
transition
} }
} }
Fade {
text: step.text.clone(),
position: step.position,
duration: step.transition,
start_time: Instant::now(),
}
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct CyberScreenPrivate { pub struct CyberScreenPrivate {
state: Rc<RefCell<CyberScreenState>>, state: Rc<RefCell<CyberScreenState>>,
transition_queue: Rc<RefCell<Vec<Transition>>>, animations: Rc<RefCell<Vec<Box<dyn Animation>>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -171,7 +211,7 @@ impl CyberScreenPrivate {
fn next_page(&self) { fn next_page(&self) {
let transition = self.state.borrow_mut().next_page(); let transition = self.state.borrow_mut().next_page();
self.transition_queue.borrow_mut().push(transition); self.animations.borrow_mut().push(Box::new(transition));
} }
} }
@ -187,6 +227,7 @@ impl CyberScreen {
s.set_draw_func({ s.set_draw_func({
let s = s.clone(); let s = s.clone();
move |_, context, width, height| { move |_, context, width, height| {
let now = Instant::now();
let _ = context.set_source_rgb(0., 0., 0.); let _ = context.set_source_rgb(0., 0., 0.);
let _ = context.paint(); let _ = context.paint();
@ -198,40 +239,27 @@ impl CyberScreen {
} }
pen.stroke(); pen.stroke();
*/ */
let tracery = pen.finish(); let tracery = pen.finish();
let _ = context.set_source(tracery); let _ = context.set_source(tracery);
let _ = context.paint(); let _ = context.paint();
for transition in s.imp().transition_queue.borrow().iter() { let mut animations = s.imp().animations.borrow_mut();
if let Some(ref text) = s.imp().state.borrow().top { let mut to_remove = vec![];
let text_display = Text::new(text.text.clone(), context, 32.); for (idx, animation) in animations.iter().enumerate() {
let y = height as f64 * 1. / 5. + text_display.extents().height(); let y = match animation.position() {
Position::Top => height as f64 * 1. / 5.,
Position::Middle => height as f64 * 2. / 5.,
Position::Bottom => height as f64 * 3. / 5.,
};
context.move_to(20., y);
let done = animation.tick(now, context);
if done {
to_remove.push(idx)
};
}
context.move_to(20., y); for idx in to_remove.into_iter() {
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2); animations.remove(idx);
text_display.draw();
}
if let Some(ref text) = s.imp().state.borrow().middle {
let text_display = Text::new(text.text.clone(), context, 32.);
let y = height as f64 * 2. / 5. + text_display.extents().height();
context.move_to(20., y);
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
text_display.draw();
}
if let Some(ref text) = s.imp().state.borrow().bottom {
let text_display = Text::new(text.text.clone(), context, 32.);
let y = height as f64 * 3. / 5. + text_display.extents().height();
context.move_to(20., y);
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
text_display.draw();
}
} }
} }
}); });
@ -247,6 +275,14 @@ impl CyberScreen {
} }
fn main() { fn main() {
let script = Script(vec![Step {
text: "The distinguishing thing".to_owned(),
position: Position::Top,
transition: Duration::from_secs(2),
}]);
println!("{}", serde_yml::to_string(&script).unwrap());
let script = Script::from_file(Path::new("./script.yml")).unwrap(); let script = Script::from_file(Path::new("./script.yml")).unwrap();
for element in script.iter() { for element in script.iter() {
println!("{:?}", element); println!("{:?}", element);
@ -277,6 +313,14 @@ fn main() {
window.set_width_request(800); window.set_width_request(800);
window.set_height_request(600); window.set_height_request(600);
window.present(); window.present();
let _ = glib::spawn_future_local({
let screen = screen.clone();
async move { loop {
screen.queue_draw();
async_std::task::sleep(Duration::from_millis(1000 / FPS)).await;
}}
});
}); });
app.run(); app.run();