Create a slideshow application in my cyberpunk style #252
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,54 +153,44 @@ 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) => {
|
||||||
|
if idx < self.script.len() {
|
||||||
idx + 1
|
idx + 1
|
||||||
} else {
|
} else {
|
||||||
idx
|
idx
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.idx = Some(idx);
|
self.idx = Some(idx);
|
||||||
let step = self.script[idx].clone();
|
let step = self.script[idx].clone();
|
||||||
|
|
||||||
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);
|
context.move_to(20., y);
|
||||||
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
|
let done = animation.tick(now, context);
|
||||||
text_display.draw();
|
if done {
|
||||||
|
to_remove.push(idx)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref text) = s.imp().state.borrow().middle {
|
for idx in to_remove.into_iter() {
|
||||||
let text_display = Text::new(text.text.clone(), context, 32.);
|
animations.remove(idx);
|
||||||
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();
|
||||||
|
|
Loading…
Reference in New Issue