2024-10-08 03:47:17 +00:00
|
|
|
use std::{
|
|
|
|
cell::RefCell,
|
|
|
|
fs::File,
|
|
|
|
io::Read,
|
|
|
|
ops::Index,
|
|
|
|
path::Path,
|
|
|
|
rc::Rc,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2024-10-05 00:27:34 +00:00
|
|
|
|
2024-10-08 03:47:17 +00:00
|
|
|
use cairo::Context;
|
2024-10-05 00:56:37 +00:00
|
|
|
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
|
2024-10-08 02:42:27 +00:00
|
|
|
use glib::{GString, Object};
|
|
|
|
use gtk::{
|
|
|
|
glib::{self, Propagation},
|
|
|
|
prelude::*,
|
|
|
|
subclass::prelude::*,
|
|
|
|
EventControllerKey,
|
|
|
|
};
|
2024-10-05 00:27:34 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2024-10-08 03:47:17 +00:00
|
|
|
const FPS: u64 = 60;
|
2024-10-08 02:42:27 +00:00
|
|
|
const PURPLE: (f64, f64, f64) = (0.7, 0., 1.);
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
2024-10-05 00:27:34 +00:00
|
|
|
#[serde(rename_all = "lowercase")]
|
2024-10-08 02:42:27 +00:00
|
|
|
enum Position {
|
2024-10-05 00:27:34 +00:00
|
|
|
Top,
|
|
|
|
Middle,
|
|
|
|
Bottom,
|
|
|
|
}
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
struct Step {
|
2024-10-05 00:27:34 +00:00
|
|
|
text: String,
|
2024-10-08 02:42:27 +00:00
|
|
|
position: Position,
|
2024-10-08 03:47:17 +00:00
|
|
|
transition: Duration,
|
2024-10-05 00:27:34 +00:00
|
|
|
}
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
struct Script(Vec<Step>);
|
2024-10-05 00:27:34 +00:00
|
|
|
|
|
|
|
impl Script {
|
|
|
|
fn from_file(path: &Path) -> Result<Script, serde_yml::Error> {
|
|
|
|
let mut buf: Vec<u8> = Vec::new();
|
|
|
|
let mut f = File::open(path).unwrap();
|
|
|
|
f.read_to_end(&mut buf).unwrap();
|
|
|
|
let script = serde_yml::from_slice(&buf)?;
|
|
|
|
Ok(Self(script))
|
|
|
|
}
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Step> {
|
2024-10-05 00:27:34 +00:00
|
|
|
self.0.iter()
|
|
|
|
}
|
2024-10-08 02:42:27 +00:00
|
|
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
self.0.len()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Script {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self(vec![])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Index<usize> for Script {
|
|
|
|
type Output = Step;
|
|
|
|
|
|
|
|
fn index(&self, index: usize) -> &Self::Output {
|
|
|
|
&self.0[index]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-08 03:47:17 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct CyberScreenState {
|
|
|
|
script: Script,
|
|
|
|
idx: Option<usize>,
|
|
|
|
top: Option<Step>,
|
|
|
|
middle: Option<Step>,
|
|
|
|
bottom: Option<Step>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for CyberScreenState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
script: Script(vec![]),
|
|
|
|
idx: None,
|
|
|
|
top: None,
|
|
|
|
middle: None,
|
|
|
|
bottom: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CyberScreenState {
|
|
|
|
fn new(script: Script) -> CyberScreenState {
|
|
|
|
let mut s = CyberScreenState::default();
|
|
|
|
s.script = script;
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
2024-10-08 03:47:17 +00:00
|
|
|
fn next_page(&mut self) -> impl Animation {
|
2024-10-08 02:42:27 +00:00
|
|
|
let idx = match self.idx {
|
|
|
|
None => 0,
|
2024-10-08 03:47:17 +00:00
|
|
|
Some(idx) => {
|
|
|
|
if idx < self.script.len() {
|
|
|
|
idx + 1
|
|
|
|
} else {
|
|
|
|
idx
|
|
|
|
}
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
self.idx = Some(idx);
|
|
|
|
let step = self.script[idx].clone();
|
|
|
|
|
|
|
|
match step.position {
|
|
|
|
Position::Top => {
|
2024-10-08 03:47:17 +00:00
|
|
|
self.top = Some(step.clone());
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
|
|
|
Position::Middle => {
|
2024-10-08 03:47:17 +00:00
|
|
|
self.middle = Some(step.clone());
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
|
|
|
Position::Bottom => {
|
2024-10-08 03:47:17 +00:00
|
|
|
self.bottom = Some(step.clone());
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
|
|
|
}
|
2024-10-08 03:47:17 +00:00
|
|
|
Fade {
|
|
|
|
text: step.text.clone(),
|
|
|
|
position: step.position,
|
|
|
|
duration: step.transition,
|
|
|
|
start_time: Instant::now(),
|
|
|
|
}
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
2024-10-05 00:27:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
2024-10-08 02:42:27 +00:00
|
|
|
pub struct CyberScreenPrivate {
|
|
|
|
state: Rc<RefCell<CyberScreenState>>,
|
2024-10-08 03:47:17 +00:00
|
|
|
animations: Rc<RefCell<Vec<Box<dyn Animation>>>>,
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
2024-10-05 00:27:34 +00:00
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
impl ObjectSubclass for CyberScreenPrivate {
|
|
|
|
const NAME: &'static str = "CyberScreen";
|
|
|
|
type Type = CyberScreen;
|
|
|
|
type ParentType = gtk::DrawingArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for CyberScreenPrivate {}
|
|
|
|
impl WidgetImpl for CyberScreenPrivate {}
|
|
|
|
impl DrawingAreaImpl for CyberScreenPrivate {}
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
impl CyberScreenPrivate {
|
|
|
|
fn set_script(&self, script: Script) {
|
|
|
|
*self.state.borrow_mut() = CyberScreenState::new(script);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn next_page(&self) {
|
|
|
|
let transition = self.state.borrow_mut().next_page();
|
2024-10-08 03:47:17 +00:00
|
|
|
self.animations.borrow_mut().push(Box::new(transition));
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-05 00:27:34 +00:00
|
|
|
glib::wrapper! {
|
|
|
|
pub struct CyberScreen(ObjectSubclass<CyberScreenPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CyberScreen {
|
2024-10-08 02:42:27 +00:00
|
|
|
pub fn new(script: Script) -> Self {
|
2024-10-05 00:27:34 +00:00
|
|
|
let s: Self = Object::builder().build();
|
2024-10-08 02:42:27 +00:00
|
|
|
s.imp().set_script(script);
|
2024-10-05 00:27:34 +00:00
|
|
|
|
|
|
|
s.set_draw_func({
|
2024-10-08 02:42:27 +00:00
|
|
|
let s = s.clone();
|
2024-10-05 00:27:34 +00:00
|
|
|
move |_, context, width, height| {
|
2024-10-08 03:47:17 +00:00
|
|
|
let now = Instant::now();
|
2024-10-05 00:27:34 +00:00
|
|
|
let _ = context.set_source_rgb(0., 0., 0.);
|
|
|
|
let _ = context.paint();
|
|
|
|
|
|
|
|
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
|
2024-10-08 02:42:27 +00:00
|
|
|
/*
|
|
|
|
for i in 0..6 {
|
|
|
|
pen.move_to(0., height as f64 * i as f64 / 5.);
|
|
|
|
pen.line_to(width as f64, height as f64 * i as f64 / 5.);
|
2024-10-05 00:27:34 +00:00
|
|
|
}
|
|
|
|
pen.stroke();
|
2024-10-08 02:42:27 +00:00
|
|
|
*/
|
2024-10-05 00:27:34 +00:00
|
|
|
let tracery = pen.finish();
|
|
|
|
let _ = context.set_source(tracery);
|
|
|
|
let _ = context.paint();
|
|
|
|
|
2024-10-08 03:47:17 +00:00
|
|
|
let mut animations = s.imp().animations.borrow_mut();
|
|
|
|
let mut to_remove = vec![];
|
|
|
|
for (idx, animation) in animations.iter().enumerate() {
|
|
|
|
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)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx in to_remove.into_iter() {
|
|
|
|
animations.remove(idx);
|
2024-10-08 02:42:27 +00:00
|
|
|
}
|
2024-10-05 00:27:34 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
s
|
|
|
|
}
|
2024-10-08 02:42:27 +00:00
|
|
|
|
|
|
|
fn next_page(&self) {
|
|
|
|
println!("next page");
|
|
|
|
self.imp().next_page();
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
2024-10-05 00:27:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
2024-10-08 03:47:17 +00:00
|
|
|
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());
|
|
|
|
|
2024-10-05 00:27:34 +00:00
|
|
|
let script = Script::from_file(Path::new("./script.yml")).unwrap();
|
|
|
|
for element in script.iter() {
|
|
|
|
println!("{:?}", element);
|
|
|
|
}
|
|
|
|
|
|
|
|
let app = gtk::Application::builder()
|
|
|
|
.application_id("com.luminescent-dreams.cyberpunk-slideshow")
|
|
|
|
.build();
|
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
app.connect_activate(move |app| {
|
|
|
|
let screen = CyberScreen::new(script.clone());
|
2024-10-05 00:27:34 +00:00
|
|
|
|
2024-10-08 02:42:27 +00:00
|
|
|
let events = EventControllerKey::new();
|
|
|
|
|
|
|
|
events.connect_key_released({
|
|
|
|
let screen = screen.clone();
|
|
|
|
move |_, key, _, _| {
|
|
|
|
if key.name() == Some(GString::from("Right")) {
|
|
|
|
screen.next_page();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let window = gtk::ApplicationWindow::new(app);
|
|
|
|
window.add_controller(events);
|
2024-10-05 00:27:34 +00:00
|
|
|
|
|
|
|
window.set_child(Some(&screen));
|
|
|
|
window.set_width_request(800);
|
|
|
|
window.set_height_request(600);
|
|
|
|
window.present();
|
2024-10-08 03:47:17 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}}
|
|
|
|
});
|
2024-10-05 00:27:34 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
app.run();
|
|
|
|
}
|